@camstack/addon-provider-dreo 0.1.5 → 0.1.6
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/dist/addon.js +378 -618
- package/dist/addon.mjs +378 -618
- package/dist/index.js +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +2 -5
package/dist/addon.js
CHANGED
|
@@ -14802,83 +14802,34 @@ var GetStateInputSchema = object({
|
|
|
14802
14802
|
* HA: entity_id (returns the cached entity state). */
|
|
14803
14803
|
key: string()
|
|
14804
14804
|
});
|
|
14805
|
-
|
|
14806
|
-
|
|
14807
|
-
|
|
14808
|
-
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
14817
|
-
|
|
14818
|
-
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
|
|
14828
|
-
|
|
14829
|
-
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
}),
|
|
14834
|
-
/** Read the persisted settings record for a broker (kind-specific
|
|
14835
|
-
* shape). Admin-only — settings may contain secrets. Returns `null`
|
|
14836
|
-
* when the broker id is unknown to the provider (the collection
|
|
14837
|
-
* fallback may route a foreign id to the first provider). */
|
|
14838
|
-
getSettings: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
14839
|
-
/** Overwrite the persisted settings record. The kind-specific
|
|
14840
|
-
* provider validates the shape and applies the change (reconnects
|
|
14841
|
-
* if credentials changed). */
|
|
14842
|
-
setSettings: method(object({
|
|
14843
|
-
id: string(),
|
|
14844
|
-
settings: SettingsRecordSchema$1
|
|
14845
|
-
}), _void(), {
|
|
14846
|
-
kind: "mutation",
|
|
14847
|
-
auth: "admin"
|
|
14848
|
-
}),
|
|
14849
|
-
/** Returns the kind-specific connection config the consumer needs
|
|
14850
|
-
* to open its own client (MQTT pattern: `{url, username, password,
|
|
14851
|
-
* clientIdPrefix}`). HA providers MAY return the auth envelope
|
|
14852
|
-
* but typical HA consumers use `publish` / `subscribe` instead.
|
|
14853
|
-
* Returns `null` when the broker id is unknown to the provider. */
|
|
14854
|
-
getBrokerConfig: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
|
|
14855
|
-
getSettingsSchema: method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }),
|
|
14856
|
-
testSettings: method(TestSettingsInputSchema, TestSettingsResultSchema, {
|
|
14857
|
-
kind: "mutation",
|
|
14858
|
-
auth: "admin"
|
|
14859
|
-
}),
|
|
14860
|
-
publish: method(PublishInputSchema, unknown(), {
|
|
14861
|
-
kind: "mutation",
|
|
14862
|
-
auth: "admin"
|
|
14863
|
-
}),
|
|
14864
|
-
subscribe: method(SubscribeInputSchema, SubscribeResultSchema, {
|
|
14865
|
-
kind: "mutation",
|
|
14866
|
-
auth: "admin"
|
|
14867
|
-
}),
|
|
14868
|
-
unsubscribe: method(UnsubscribeInputSchema, _void(), {
|
|
14869
|
-
kind: "mutation",
|
|
14870
|
-
auth: "admin"
|
|
14871
|
-
}),
|
|
14872
|
-
/** Read the broker's cached state for a key. Returns `null` when
|
|
14873
|
-
* unknown to the broker (never published / unknown entity). */
|
|
14874
|
-
getState: method(GetStateInputSchema, unknown().nullable()),
|
|
14875
|
-
/** Status method — explicit registration with a `z.void()` input so
|
|
14876
|
-
* the codegen-generated tRPC router types its input as
|
|
14877
|
-
* `{addonId?: string, nodeId?: string}` (system-scoped collection
|
|
14878
|
-
* shape) instead of the device-scoped `{deviceId}` fallback. */
|
|
14879
|
-
getStatus: method(_void(), RegistryStatusSchema)
|
|
14880
|
-
}
|
|
14881
|
-
};
|
|
14805
|
+
method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, BrokerInfoSchema$1.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
|
|
14806
|
+
kind: "mutation",
|
|
14807
|
+
auth: "admin"
|
|
14808
|
+
}), method(RemoveInputSchema, _void(), {
|
|
14809
|
+
kind: "mutation",
|
|
14810
|
+
auth: "admin"
|
|
14811
|
+
}), method(GetInputSchema, TestConnectionResultSchema, {
|
|
14812
|
+
kind: "mutation",
|
|
14813
|
+
auth: "admin"
|
|
14814
|
+
}), method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }), method(object({
|
|
14815
|
+
id: string(),
|
|
14816
|
+
settings: SettingsRecordSchema$1
|
|
14817
|
+
}), _void(), {
|
|
14818
|
+
kind: "mutation",
|
|
14819
|
+
auth: "admin"
|
|
14820
|
+
}), method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }), method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }), method(TestSettingsInputSchema, TestSettingsResultSchema, {
|
|
14821
|
+
kind: "mutation",
|
|
14822
|
+
auth: "admin"
|
|
14823
|
+
}), method(PublishInputSchema, unknown(), {
|
|
14824
|
+
kind: "mutation",
|
|
14825
|
+
auth: "admin"
|
|
14826
|
+
}), method(SubscribeInputSchema, SubscribeResultSchema, {
|
|
14827
|
+
kind: "mutation",
|
|
14828
|
+
auth: "admin"
|
|
14829
|
+
}), method(UnsubscribeInputSchema, _void(), {
|
|
14830
|
+
kind: "mutation",
|
|
14831
|
+
auth: "admin"
|
|
14832
|
+
}), method(GetStateInputSchema, unknown().nullable()), method(_void(), RegistryStatusSchema);
|
|
14882
14833
|
DeviceType.Camera;
|
|
14883
14834
|
/**
|
|
14884
14835
|
* `custom-model-registry` — collection cap exposing operator-registered
|
|
@@ -18333,6 +18284,17 @@ var AvailableIntegrationTypeSchema = object({
|
|
|
18333
18284
|
iconUrl: string().nullable(),
|
|
18334
18285
|
color: string(),
|
|
18335
18286
|
instanceMode: string(),
|
|
18287
|
+
/**
|
|
18288
|
+
* Integration wizard `mode` (LOCKED MODEL): `standalone` (create
|
|
18289
|
+
* immediately then add devices, no config step/button), `account` (config
|
|
18290
|
+
* step), or `broker` (broker step). Derived server-side by
|
|
18291
|
+
* `getAvailableTypes` when the addon manifest omits an explicit `mode`.
|
|
18292
|
+
*/
|
|
18293
|
+
mode: _enum([
|
|
18294
|
+
"standalone",
|
|
18295
|
+
"account",
|
|
18296
|
+
"broker"
|
|
18297
|
+
]),
|
|
18336
18298
|
discoveryMode: string(),
|
|
18337
18299
|
/**
|
|
18338
18300
|
* Which integration-marker cap the addon declared, so the wizard can
|
|
@@ -24051,6 +24013,89 @@ object({
|
|
|
24051
24013
|
schemaVersion: literal(1)
|
|
24052
24014
|
});
|
|
24053
24015
|
//#endregion
|
|
24016
|
+
//#region src/config.ts
|
|
24017
|
+
/**
|
|
24018
|
+
* Dreo cloud regions the wrapped `@apocaliss92/nodedreo` client accepts. The
|
|
24019
|
+
* library does not export a region enum (region is a free `opts.region` string),
|
|
24020
|
+
* so we mirror the common Dreo regions locally and validate the operator-supplied
|
|
24021
|
+
* value at the system boundary. `us` is the default (the most common Dreo cloud).
|
|
24022
|
+
*/
|
|
24023
|
+
var DREO_REGIONS = ["us", "eu"];
|
|
24024
|
+
var DreoRegionSchema = _enum(DREO_REGIONS);
|
|
24025
|
+
/**
|
|
24026
|
+
* Operator-supplied Dreo account settings for ONE integration (= one cloud
|
|
24027
|
+
* account). The Dreo integration is cloud-only (REST auth + a persistent
|
|
24028
|
+
* WebSocket push), so the connection is just account credentials plus a region.
|
|
24029
|
+
*/
|
|
24030
|
+
var dreoConfigSchema = object({
|
|
24031
|
+
email: string().min(1).describe("Dreo account email"),
|
|
24032
|
+
password: string().min(1).describe("Dreo account password"),
|
|
24033
|
+
region: DreoRegionSchema.default("us").describe("Dreo cloud region")
|
|
24034
|
+
});
|
|
24035
|
+
/**
|
|
24036
|
+
* Split the validated addon config into the two arguments the `Nodedreo`
|
|
24037
|
+
* constructor expects (`creds`, `opts`). Pure: same config in → same args out.
|
|
24038
|
+
*/
|
|
24039
|
+
function toDreoConstructorArgs(config) {
|
|
24040
|
+
return {
|
|
24041
|
+
creds: {
|
|
24042
|
+
email: config.email,
|
|
24043
|
+
password: config.password
|
|
24044
|
+
},
|
|
24045
|
+
opts: { region: config.region }
|
|
24046
|
+
};
|
|
24047
|
+
}
|
|
24048
|
+
/**
|
|
24049
|
+
* Parse an integration's settings into a connection, or null when the mandatory
|
|
24050
|
+
* credentials (email + password) are missing. Used by the boot/lifecycle
|
|
24051
|
+
* reconcile to skip an integration whose account form was not completed.
|
|
24052
|
+
*/
|
|
24053
|
+
function connectionFromSettings(settings) {
|
|
24054
|
+
const parsed = dreoConfigSchema.safeParse(settings);
|
|
24055
|
+
return parsed.success ? parsed.data : null;
|
|
24056
|
+
}
|
|
24057
|
+
/**
|
|
24058
|
+
* Hand-written connection form for the account/integration creation UI — the
|
|
24059
|
+
* wizard's `account` config step renders this via `getGlobalSettings`. A flat
|
|
24060
|
+
* set of sections the admin UI renders into the "Add Dreo account" modal.
|
|
24061
|
+
*/
|
|
24062
|
+
function buildConnectionFormSchema() {
|
|
24063
|
+
return { sections: [{
|
|
24064
|
+
id: "credentials",
|
|
24065
|
+
title: "Dreo account",
|
|
24066
|
+
description: "Sign in with your Dreo app account credentials.",
|
|
24067
|
+
columns: 1,
|
|
24068
|
+
fields: [{
|
|
24069
|
+
type: "text",
|
|
24070
|
+
key: "email",
|
|
24071
|
+
label: "Email",
|
|
24072
|
+
required: true,
|
|
24073
|
+
placeholder: "you@example.com"
|
|
24074
|
+
}, {
|
|
24075
|
+
type: "password",
|
|
24076
|
+
key: "password",
|
|
24077
|
+
label: "Password",
|
|
24078
|
+
required: true,
|
|
24079
|
+
showToggle: true
|
|
24080
|
+
}]
|
|
24081
|
+
}, {
|
|
24082
|
+
id: "region",
|
|
24083
|
+
title: "Region",
|
|
24084
|
+
description: "Select the Dreo cloud region your account is registered in.",
|
|
24085
|
+
columns: 1,
|
|
24086
|
+
fields: [{
|
|
24087
|
+
type: "select",
|
|
24088
|
+
key: "region",
|
|
24089
|
+
label: "Region",
|
|
24090
|
+
default: "us",
|
|
24091
|
+
options: DREO_REGIONS.map((r) => ({
|
|
24092
|
+
value: r,
|
|
24093
|
+
label: r.toUpperCase()
|
|
24094
|
+
}))
|
|
24095
|
+
}]
|
|
24096
|
+
}] };
|
|
24097
|
+
}
|
|
24098
|
+
//#endregion
|
|
24054
24099
|
//#region ../../node_modules/ws/lib/constants.js
|
|
24055
24100
|
var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
24056
24101
|
var BINARY_TYPES = [
|
|
@@ -28896,99 +28941,6 @@ var Nodedreo = class {
|
|
|
28896
28941
|
}
|
|
28897
28942
|
};
|
|
28898
28943
|
//#endregion
|
|
28899
|
-
//#region src/config.ts
|
|
28900
|
-
/**
|
|
28901
|
-
* Dreo cloud regions the wrapped `@apocaliss92/nodedreo` client accepts. The
|
|
28902
|
-
* library does not export a region enum (region is a free `opts.region` string),
|
|
28903
|
-
* so we mirror the common Dreo regions locally and validate the operator-supplied
|
|
28904
|
-
* value at the system boundary. `us` is the default (the most common Dreo cloud).
|
|
28905
|
-
*/
|
|
28906
|
-
var DREO_REGIONS = ["us", "eu"];
|
|
28907
|
-
var DreoRegionSchema = _enum(DREO_REGIONS);
|
|
28908
|
-
/**
|
|
28909
|
-
* Operator-supplied Dreo account settings for ONE broker (= one cloud account).
|
|
28910
|
-
* The Dreo integration is cloud-only (REST auth + a persistent WebSocket push),
|
|
28911
|
-
* so the connection is just account credentials plus a region.
|
|
28912
|
-
*/
|
|
28913
|
-
var dreoConfigSchema = object({
|
|
28914
|
-
email: string().min(1).describe("Dreo account email"),
|
|
28915
|
-
password: string().min(1).describe("Dreo account password"),
|
|
28916
|
-
region: DreoRegionSchema.default("us").describe("Dreo cloud region")
|
|
28917
|
-
});
|
|
28918
|
-
/**
|
|
28919
|
-
* Split the validated addon config into the two arguments the `Nodedreo`
|
|
28920
|
-
* constructor expects (`creds`, `opts`). Pure: same config in → same args out.
|
|
28921
|
-
*/
|
|
28922
|
-
function toDreoConstructorArgs(config) {
|
|
28923
|
-
return {
|
|
28924
|
-
creds: {
|
|
28925
|
-
email: config.email,
|
|
28926
|
-
password: config.password
|
|
28927
|
-
},
|
|
28928
|
-
opts: { region: config.region }
|
|
28929
|
-
};
|
|
28930
|
-
}
|
|
28931
|
-
/** Top-level addon config — an ordered list of broker entries (default empty). */
|
|
28932
|
-
var dreoAddonConfigSchema = object({ brokers: array(object({
|
|
28933
|
-
/** Stable opaque identifier — e.g. 'dreo_001', 'dreo_002'. */
|
|
28934
|
-
id: string().min(1),
|
|
28935
|
-
/** Human-readable label shown in the admin UI. */
|
|
28936
|
-
name: string().min(1),
|
|
28937
|
-
/** Validated account connection settings. */
|
|
28938
|
-
connection: dreoConfigSchema,
|
|
28939
|
-
/** FK to the spawning integration — auto-cleanup on integration delete. */
|
|
28940
|
-
integrationId: string().optional()
|
|
28941
|
-
})).default([]) });
|
|
28942
|
-
/**
|
|
28943
|
-
* Coerce a loose settings blob (from the broker `add`/`setSettings` cap) through
|
|
28944
|
-
* the connection schema, applying all defaults. Throws a `ZodError` on invalid
|
|
28945
|
-
* input so the caller surfaces a clear error at the system boundary.
|
|
28946
|
-
*/
|
|
28947
|
-
function settingsToDreoConfig(settings) {
|
|
28948
|
-
return dreoConfigSchema.parse(settings ?? {});
|
|
28949
|
-
}
|
|
28950
|
-
/**
|
|
28951
|
-
* Hand-written connection form for the broker/integration creation UI. Mirrors
|
|
28952
|
-
* the Dreame / Homematic broker-settings form shape — a flat set of sections the
|
|
28953
|
-
* admin UI renders into the "Add Dreo account" modal.
|
|
28954
|
-
*/
|
|
28955
|
-
function buildConnectionFormSchema() {
|
|
28956
|
-
return { sections: [{
|
|
28957
|
-
id: "credentials",
|
|
28958
|
-
title: "Dreo account",
|
|
28959
|
-
description: "Sign in with your Dreo app account credentials.",
|
|
28960
|
-
columns: 1,
|
|
28961
|
-
fields: [{
|
|
28962
|
-
type: "text",
|
|
28963
|
-
key: "email",
|
|
28964
|
-
label: "Email",
|
|
28965
|
-
required: true,
|
|
28966
|
-
placeholder: "you@example.com"
|
|
28967
|
-
}, {
|
|
28968
|
-
type: "password",
|
|
28969
|
-
key: "password",
|
|
28970
|
-
label: "Password",
|
|
28971
|
-
required: true,
|
|
28972
|
-
showToggle: true
|
|
28973
|
-
}]
|
|
28974
|
-
}, {
|
|
28975
|
-
id: "region",
|
|
28976
|
-
title: "Region",
|
|
28977
|
-
description: "Select the Dreo cloud region your account is registered in.",
|
|
28978
|
-
columns: 1,
|
|
28979
|
-
fields: [{
|
|
28980
|
-
type: "select",
|
|
28981
|
-
key: "region",
|
|
28982
|
-
label: "Region",
|
|
28983
|
-
default: "us",
|
|
28984
|
-
options: DREO_REGIONS.map((r) => ({
|
|
28985
|
-
value: r,
|
|
28986
|
-
label: r.toUpperCase()
|
|
28987
|
-
}))
|
|
28988
|
-
}]
|
|
28989
|
-
}] };
|
|
28990
|
-
}
|
|
28991
|
-
//#endregion
|
|
28992
28944
|
//#region src/dreo-integration-manager.ts
|
|
28993
28945
|
function defaultFacade(config) {
|
|
28994
28946
|
const { creds, opts } = toDreoConstructorArgs(config);
|
|
@@ -29146,15 +29098,20 @@ function sameConnection(a, b) {
|
|
|
29146
29098
|
//#endregion
|
|
29147
29099
|
//#region src/dreo-gateway.ts
|
|
29148
29100
|
/**
|
|
29149
|
-
* Per-
|
|
29101
|
+
* Per-integration registry that device classes use to reach a live Dreo device
|
|
29150
29102
|
* handle (and the discovery info) for a given Dreo account.
|
|
29151
29103
|
*
|
|
29104
|
+
* `mode: account`: keyed by `integrationId`. The string key is generically named
|
|
29105
|
+
* `brokerId` in the method signatures below because the device classes pass their
|
|
29106
|
+
* persisted `brokerId` config field — which now holds the integrationId
|
|
29107
|
+
* (unchanged device-side code, see the addon migration note).
|
|
29108
|
+
*
|
|
29152
29109
|
* The kernel constructs device classes with only a `DeviceContext` — it cannot
|
|
29153
29110
|
* thread the handle in as a constructor arg. Like the Dreame addon's
|
|
29154
29111
|
* `dreameFacades`, we keep it simple and in-process: the integration manager
|
|
29155
29112
|
* owns the connection surface per registered account and publishes it here;
|
|
29156
29113
|
* device classes resolve their live `BaseDevice` handle by `(brokerId,
|
|
29157
|
-
* deviceId)
|
|
29114
|
+
* deviceId)` where `brokerId` = integrationId.
|
|
29158
29115
|
*/
|
|
29159
29116
|
var DreoConnectionResolver = class {
|
|
29160
29117
|
#surfaces = /* @__PURE__ */ new Map();
|
|
@@ -29191,31 +29148,51 @@ var DreoConnectionResolver = class {
|
|
|
29191
29148
|
this.#surfaces.clear();
|
|
29192
29149
|
}
|
|
29193
29150
|
};
|
|
29194
|
-
/** The single in-process per-
|
|
29195
|
-
* registry/manager and device instances. */
|
|
29151
|
+
/** The single in-process per-integration connection resolver shared between the
|
|
29152
|
+
* client registry/manager and device instances (keyed by integrationId). */
|
|
29196
29153
|
var dreoConnections = new DreoConnectionResolver();
|
|
29197
29154
|
//#endregion
|
|
29198
|
-
//#region src/dreo-
|
|
29155
|
+
//#region src/dreo-client-registry.ts
|
|
29199
29156
|
/**
|
|
29200
|
-
*
|
|
29201
|
-
*
|
|
29202
|
-
*
|
|
29203
|
-
*
|
|
29157
|
+
* Per-integration Dreo connection registry (account mode).
|
|
29158
|
+
*
|
|
29159
|
+
* Under the LOCKED integration/adoption model (design §7.1), Dreo is a
|
|
29160
|
+
* `mode: account` addon: each Dreo integration carries its own Dreo cloud
|
|
29161
|
+
* account credentials in its `integration.settings`, and this registry holds one
|
|
29162
|
+
* {@link DreoIntegrationManager} per `integrationId`
|
|
29163
|
+
* (`Map<integrationId, DreoIntegrationManager>`). Multi-account = multiple
|
|
29164
|
+
* integrations; there is NO shared broker.
|
|
29165
|
+
*
|
|
29166
|
+
* The live Dreo connection surface each manager owns (discovery info + per-device
|
|
29167
|
+
* handles) is published on the in-process {@link dreoConnections} resolver KEYED
|
|
29168
|
+
* BY `integrationId` — the manager's id IS the integrationId. Device classes
|
|
29169
|
+
* resolve their live handle by the `brokerId` config field they persist, which
|
|
29170
|
+
* now holds the integrationId (unchanged device-side code, see the addon
|
|
29171
|
+
* migration note). The WebSocket push lifecycle stays entirely inside the manager
|
|
29172
|
+
* + library (no leak): the registry only starts/stops managers.
|
|
29173
|
+
*/
|
|
29174
|
+
/**
|
|
29175
|
+
* Owns the `Map<integrationId, DreoIntegrationManager>`. Callers reconcile the
|
|
29176
|
+
* map against the live integration list on boot + on every integration lifecycle
|
|
29177
|
+
* event; device classes + the adoption provider resolve their surface by
|
|
29178
|
+
* `integrationId` through {@link dreoConnections}.
|
|
29179
|
+
*
|
|
29180
|
+
* Structural twin of `DreameClientRegistry` / `WyzeClientRegistry` — `upsert` is
|
|
29181
|
+
* session-preserving when credentials are unchanged, `retain` drops vanished
|
|
29182
|
+
* integrations.
|
|
29204
29183
|
*/
|
|
29205
|
-
var
|
|
29184
|
+
var DreoClientRegistry = class {
|
|
29206
29185
|
#logger;
|
|
29207
|
-
#
|
|
29208
|
-
#
|
|
29186
|
+
#onConnected;
|
|
29187
|
+
#onDisconnected;
|
|
29209
29188
|
#makeManager;
|
|
29210
|
-
#
|
|
29211
|
-
#integrationToBroker = /* @__PURE__ */ new Map();
|
|
29212
|
-
#nextId = 1;
|
|
29189
|
+
#entries = /* @__PURE__ */ new Map();
|
|
29213
29190
|
constructor(logger, deps = {}) {
|
|
29214
29191
|
this.#logger = logger;
|
|
29215
|
-
this.#
|
|
29216
|
-
this.#
|
|
29192
|
+
this.#onConnected = deps.onConnected ?? (() => void 0);
|
|
29193
|
+
this.#onDisconnected = deps.onDisconnected ?? (() => void 0);
|
|
29217
29194
|
this.#makeManager = deps.makeManager ?? ((opts) => new DreoIntegrationManager({
|
|
29218
|
-
id: opts.
|
|
29195
|
+
id: opts.integrationId,
|
|
29219
29196
|
name: opts.name,
|
|
29220
29197
|
connection: opts.connection,
|
|
29221
29198
|
logger: opts.logger,
|
|
@@ -29224,259 +29201,119 @@ var DreoBrokerRegistry = class {
|
|
|
29224
29201
|
surfaceSink: dreoConnections
|
|
29225
29202
|
}));
|
|
29226
29203
|
}
|
|
29227
|
-
|
|
29228
|
-
|
|
29229
|
-
for (const entry of entries) this.#seedCounter(entry.id);
|
|
29230
|
-
for (const entry of entries) try {
|
|
29231
|
-
await this.#startManager(entry);
|
|
29232
|
-
} catch (err) {
|
|
29233
|
-
this.#logger.warn("DreoBrokerRegistry: failed to restore manager", {
|
|
29234
|
-
tags: { brokerId: entry.id },
|
|
29235
|
-
meta: { error: errMsg(err) }
|
|
29236
|
-
});
|
|
29237
|
-
}
|
|
29204
|
+
setOnConnected(cb) {
|
|
29205
|
+
this.#onConnected = cb;
|
|
29238
29206
|
}
|
|
29239
|
-
|
|
29240
|
-
|
|
29241
|
-
|
|
29242
|
-
|
|
29207
|
+
setOnDisconnected(cb) {
|
|
29208
|
+
this.#onDisconnected = cb;
|
|
29209
|
+
}
|
|
29210
|
+
/**
|
|
29211
|
+
* Ensure a manager exists for `integrationId` with the given credentials.
|
|
29212
|
+
* Idempotent: an existing entry with identical credentials is preserved (keeps
|
|
29213
|
+
* its live WebSocket session); a credentials change re-applies the connection
|
|
29214
|
+
* atomically (stop + start). Best-effort start — a failed login leaves the
|
|
29215
|
+
* entry registered so a later reconcile / lifecycle event retries.
|
|
29216
|
+
*/
|
|
29217
|
+
async upsert(integrationId, name, connection) {
|
|
29218
|
+
const existing = this.#entries.get(integrationId);
|
|
29219
|
+
if (existing) {
|
|
29220
|
+
if (sameConnection(existing.connection, connection)) return;
|
|
29243
29221
|
try {
|
|
29244
|
-
await
|
|
29222
|
+
await existing.manager.applyConnection(connection);
|
|
29223
|
+
this.#entries.set(integrationId, {
|
|
29224
|
+
manager: existing.manager,
|
|
29225
|
+
connection
|
|
29226
|
+
});
|
|
29245
29227
|
} catch (err) {
|
|
29246
|
-
this.#logger.warn("
|
|
29247
|
-
tags: {
|
|
29228
|
+
this.#logger.warn("DreoClientRegistry: applyConnection failed", {
|
|
29229
|
+
tags: { integrationId },
|
|
29248
29230
|
meta: { error: errMsg(err) }
|
|
29249
29231
|
});
|
|
29250
29232
|
}
|
|
29251
|
-
|
|
29252
|
-
|
|
29253
|
-
this.#
|
|
29254
|
-
|
|
29255
|
-
}
|
|
29256
|
-
setOnBrokerConnected(cb) {
|
|
29257
|
-
this.#onBrokerConnected = cb;
|
|
29258
|
-
}
|
|
29259
|
-
setOnBrokerDisconnected(cb) {
|
|
29260
|
-
this.#onBrokerDisconnected = cb;
|
|
29261
|
-
}
|
|
29262
|
-
async createEntry(name, connection, opts = {}) {
|
|
29263
|
-
const id = this.#allocateId();
|
|
29264
|
-
const entry = {
|
|
29265
|
-
id,
|
|
29233
|
+
return;
|
|
29234
|
+
}
|
|
29235
|
+
const manager = this.#makeManager({
|
|
29236
|
+
integrationId,
|
|
29266
29237
|
name,
|
|
29267
29238
|
connection,
|
|
29268
|
-
|
|
29269
|
-
|
|
29270
|
-
|
|
29271
|
-
|
|
29272
|
-
|
|
29273
|
-
|
|
29274
|
-
|
|
29275
|
-
|
|
29276
|
-
if (!mgr) throw new Error(`DreoBrokerRegistry: unknown broker id "${id}"`);
|
|
29277
|
-
for (const [integrationId, brokerId] of this.#integrationToBroker.entries()) if (brokerId === id) this.#integrationToBroker.delete(integrationId);
|
|
29278
|
-
this.#managers.delete(id);
|
|
29239
|
+
logger: this.#logger,
|
|
29240
|
+
onConnected: (id) => this.#onConnected(id),
|
|
29241
|
+
onDisconnected: (id) => this.#onDisconnected(id)
|
|
29242
|
+
});
|
|
29243
|
+
this.#entries.set(integrationId, {
|
|
29244
|
+
manager,
|
|
29245
|
+
connection
|
|
29246
|
+
});
|
|
29279
29247
|
try {
|
|
29280
|
-
await
|
|
29248
|
+
await manager.start();
|
|
29281
29249
|
} catch (err) {
|
|
29282
|
-
this.#logger.warn("
|
|
29283
|
-
tags: {
|
|
29250
|
+
this.#logger.warn("DreoClientRegistry: manager start failed", {
|
|
29251
|
+
tags: { integrationId },
|
|
29284
29252
|
meta: { error: errMsg(err) }
|
|
29285
29253
|
});
|
|
29286
29254
|
}
|
|
29287
29255
|
}
|
|
29288
|
-
|
|
29289
|
-
|
|
29290
|
-
|
|
29291
|
-
|
|
29292
|
-
|
|
29293
|
-
|
|
29294
|
-
|
|
29295
|
-
|
|
29296
|
-
|
|
29256
|
+
/** Stop + drop the manager for an integration that no longer exists. */
|
|
29257
|
+
async remove(integrationId) {
|
|
29258
|
+
const entry = this.#entries.get(integrationId);
|
|
29259
|
+
if (!entry) return;
|
|
29260
|
+
this.#entries.delete(integrationId);
|
|
29261
|
+
try {
|
|
29262
|
+
await entry.manager.stop();
|
|
29263
|
+
} catch (err) {
|
|
29264
|
+
this.#logger.warn("DreoClientRegistry: remove stop failed", {
|
|
29265
|
+
tags: { integrationId },
|
|
29266
|
+
meta: { error: errMsg(err) }
|
|
29267
|
+
});
|
|
29268
|
+
}
|
|
29297
29269
|
}
|
|
29298
|
-
|
|
29299
|
-
|
|
29270
|
+
/** Stop + drop every manager whose integrationId is not in `keep`. */
|
|
29271
|
+
async retain(keep) {
|
|
29272
|
+
const stale = [...this.#entries.keys()].filter((id) => !keep.has(id));
|
|
29273
|
+
for (const id of stale) await this.remove(id);
|
|
29300
29274
|
}
|
|
29301
|
-
|
|
29302
|
-
|
|
29303
|
-
|
|
29304
|
-
|
|
29275
|
+
/** Stop every manager and clear all state (full shutdown). */
|
|
29276
|
+
async shutdown() {
|
|
29277
|
+
const ids = [...this.#entries.keys()];
|
|
29278
|
+
await Promise.all(ids.map(async (id) => {
|
|
29279
|
+
try {
|
|
29280
|
+
await this.#entries.get(id)?.manager.stop();
|
|
29281
|
+
} catch (err) {
|
|
29282
|
+
this.#logger.warn("DreoClientRegistry: shutdown stop failed", {
|
|
29283
|
+
tags: { integrationId: id },
|
|
29284
|
+
meta: { error: errMsg(err) }
|
|
29285
|
+
});
|
|
29286
|
+
}
|
|
29287
|
+
}));
|
|
29288
|
+
this.#entries.clear();
|
|
29289
|
+
dreoConnections.clear();
|
|
29305
29290
|
}
|
|
29291
|
+
/** True when a manager is registered for the integration. */
|
|
29292
|
+
has(integrationId) {
|
|
29293
|
+
return this.#entries.has(integrationId);
|
|
29294
|
+
}
|
|
29295
|
+
/** The registered integration ids (one per account). */
|
|
29306
29296
|
list() {
|
|
29307
|
-
return
|
|
29297
|
+
return [...this.#entries.keys()];
|
|
29308
29298
|
}
|
|
29309
|
-
|
|
29310
|
-
|
|
29299
|
+
/** The current connection an integration's manager is configured with. */
|
|
29300
|
+
getConnection(integrationId) {
|
|
29301
|
+
return this.#entries.get(integrationId)?.connection ?? null;
|
|
29311
29302
|
}
|
|
29312
|
-
|
|
29313
|
-
|
|
29303
|
+
/** Immutable status snapshot for an integration's manager. */
|
|
29304
|
+
getInfo(integrationId) {
|
|
29305
|
+
return this.#entries.get(integrationId)?.manager.getInfo() ?? null;
|
|
29314
29306
|
}
|
|
29315
29307
|
size() {
|
|
29316
|
-
return this.#
|
|
29308
|
+
return this.#entries.size;
|
|
29317
29309
|
}
|
|
29318
29310
|
connectedCount() {
|
|
29319
29311
|
let count = 0;
|
|
29320
|
-
for (const
|
|
29312
|
+
for (const entry of this.#entries.values()) if (entry.manager.getInfo().status === "connected") count++;
|
|
29321
29313
|
return count;
|
|
29322
29314
|
}
|
|
29323
|
-
async #startManager(entry) {
|
|
29324
|
-
if (this.#managers.has(entry.id)) {
|
|
29325
|
-
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
29326
|
-
return;
|
|
29327
|
-
}
|
|
29328
|
-
const mgr = this.#makeManager({
|
|
29329
|
-
id: entry.id,
|
|
29330
|
-
name: entry.name,
|
|
29331
|
-
connection: entry.connection,
|
|
29332
|
-
logger: this.#logger,
|
|
29333
|
-
onConnected: (id) => this.#onBrokerConnected(id),
|
|
29334
|
-
onDisconnected: (id) => this.#onBrokerDisconnected(id)
|
|
29335
|
-
});
|
|
29336
|
-
this.#managers.set(entry.id, mgr);
|
|
29337
|
-
if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
|
|
29338
|
-
await mgr.start();
|
|
29339
|
-
}
|
|
29340
|
-
#allocateId() {
|
|
29341
|
-
const id = `dreo_${String(this.#nextId).padStart(3, "0")}`;
|
|
29342
|
-
this.#nextId++;
|
|
29343
|
-
return id;
|
|
29344
|
-
}
|
|
29345
|
-
#seedCounter(id) {
|
|
29346
|
-
const match = /^dreo_(\d+)$/.exec(id);
|
|
29347
|
-
if (match === null || match[1] === void 0) return;
|
|
29348
|
-
const n = parseInt(match[1], 10);
|
|
29349
|
-
if (!isNaN(n)) this.#nextId = Math.max(this.#nextId, n + 1);
|
|
29350
|
-
}
|
|
29351
29315
|
};
|
|
29352
29316
|
//#endregion
|
|
29353
|
-
//#region src/dreo-broker-provider.ts
|
|
29354
|
-
/** Kind tag registered by this provider — matches the `listProviders` entry. */
|
|
29355
|
-
var DREO_KIND = "dreo";
|
|
29356
|
-
/**
|
|
29357
|
-
* Construct the `broker` cap provider for the Dreo addon. Pure builder: all
|
|
29358
|
-
* side-effecting deps are injected so the returned object is unit-testable.
|
|
29359
|
-
* Mirrors `buildDreameBrokerProvider`.
|
|
29360
|
-
*/
|
|
29361
|
-
function buildDreoBrokerProvider(deps) {
|
|
29362
|
-
const { ownerAddonId, registry, getBrokers, persistBrokers, cascadeRemoveDevices, logger } = deps;
|
|
29363
|
-
const owns = (id) => getBrokers().some((b) => b.id === id);
|
|
29364
|
-
return {
|
|
29365
|
-
list: async ({ kind }) => {
|
|
29366
|
-
if (kind && kind !== DREO_KIND) return [];
|
|
29367
|
-
return registry.list().map((info) => ({
|
|
29368
|
-
...info,
|
|
29369
|
-
addonId: ownerAddonId
|
|
29370
|
-
}));
|
|
29371
|
-
},
|
|
29372
|
-
get: async ({ id }) => {
|
|
29373
|
-
const info = registry.get(id);
|
|
29374
|
-
return info ? {
|
|
29375
|
-
...info,
|
|
29376
|
-
addonId: ownerAddonId
|
|
29377
|
-
} : null;
|
|
29378
|
-
},
|
|
29379
|
-
listProviders: async () => [{
|
|
29380
|
-
addonId: ownerAddonId,
|
|
29381
|
-
kinds: [{
|
|
29382
|
-
kind: DREO_KIND,
|
|
29383
|
-
label: "Dreo"
|
|
29384
|
-
}]
|
|
29385
|
-
}],
|
|
29386
|
-
add: async ({ kind, name, settings }) => {
|
|
29387
|
-
if (kind !== DREO_KIND) throw new Error(`provider-dreo: only kind '${DREO_KIND}' is handled here (got '${kind}')`);
|
|
29388
|
-
const entry = await registry.createEntry(name, settingsToDreoConfig(settings));
|
|
29389
|
-
await persistBrokers([...getBrokers(), entry]);
|
|
29390
|
-
return { id: entry.id };
|
|
29391
|
-
},
|
|
29392
|
-
remove: async ({ id }) => {
|
|
29393
|
-
if (!owns(id)) return;
|
|
29394
|
-
await registry.removeEntry(id);
|
|
29395
|
-
await cascadeRemoveDevices(id).catch((err) => {
|
|
29396
|
-
logger.warn("dreo: broker cascade-remove threw", {
|
|
29397
|
-
tags: { brokerId: id },
|
|
29398
|
-
meta: { error: errMsg(err) }
|
|
29399
|
-
});
|
|
29400
|
-
});
|
|
29401
|
-
await persistBrokers(getBrokers().filter((b) => b.id !== id));
|
|
29402
|
-
},
|
|
29403
|
-
testConnection: async ({ id }) => {
|
|
29404
|
-
if (!owns(id)) return {
|
|
29405
|
-
ok: false,
|
|
29406
|
-
error: "unknown broker"
|
|
29407
|
-
};
|
|
29408
|
-
const info = registry.get(id);
|
|
29409
|
-
if (!info) return {
|
|
29410
|
-
ok: false,
|
|
29411
|
-
error: "unknown broker"
|
|
29412
|
-
};
|
|
29413
|
-
if (info.status === "connected") return {
|
|
29414
|
-
ok: true,
|
|
29415
|
-
latencyMs: info.lastCheckedAt ? Math.max(0, Date.now() - info.lastCheckedAt) : 0
|
|
29416
|
-
};
|
|
29417
|
-
return {
|
|
29418
|
-
ok: false,
|
|
29419
|
-
error: info.error ?? `status: ${info.status}`
|
|
29420
|
-
};
|
|
29421
|
-
},
|
|
29422
|
-
getSettings: async ({ id }) => {
|
|
29423
|
-
const entry = getBrokers().find((b) => b.id === id);
|
|
29424
|
-
if (!entry) return null;
|
|
29425
|
-
return {
|
|
29426
|
-
...entry.connection,
|
|
29427
|
-
password: ""
|
|
29428
|
-
};
|
|
29429
|
-
},
|
|
29430
|
-
setSettings: async ({ id, settings }) => {
|
|
29431
|
-
if (!owns(id)) return;
|
|
29432
|
-
const existing = getBrokers().find((b) => b.id === id);
|
|
29433
|
-
if (!existing) return;
|
|
29434
|
-
const parsed = settingsToDreoConfig(settings);
|
|
29435
|
-
const merged = {
|
|
29436
|
-
...parsed,
|
|
29437
|
-
password: parsed.password.length > 0 ? parsed.password : existing.connection.password
|
|
29438
|
-
};
|
|
29439
|
-
const updated = await registry.updateEntry(id, merged);
|
|
29440
|
-
await persistBrokers(getBrokers().map((b) => b.id === id ? updated : b));
|
|
29441
|
-
},
|
|
29442
|
-
getBrokerConfig: async ({ id }) => {
|
|
29443
|
-
const entry = getBrokers().find((b) => b.id === id);
|
|
29444
|
-
if (!entry) return null;
|
|
29445
|
-
return {
|
|
29446
|
-
...entry.connection,
|
|
29447
|
-
password: ""
|
|
29448
|
-
};
|
|
29449
|
-
},
|
|
29450
|
-
getSettingsSchema: async ({ kind }) => {
|
|
29451
|
-
if (kind !== DREO_KIND) return null;
|
|
29452
|
-
return buildConnectionFormSchema();
|
|
29453
|
-
},
|
|
29454
|
-
testSettings: async ({ kind, settings }) => {
|
|
29455
|
-
if (kind !== DREO_KIND) return {
|
|
29456
|
-
ok: false,
|
|
29457
|
-
error: `unsupported kind: ${kind}`
|
|
29458
|
-
};
|
|
29459
|
-
try {
|
|
29460
|
-
settingsToDreoConfig(settings);
|
|
29461
|
-
return { ok: true };
|
|
29462
|
-
} catch (err) {
|
|
29463
|
-
return {
|
|
29464
|
-
ok: false,
|
|
29465
|
-
error: errMsg(err)
|
|
29466
|
-
};
|
|
29467
|
-
}
|
|
29468
|
-
},
|
|
29469
|
-
publish: async () => null,
|
|
29470
|
-
subscribe: async () => ({ subscriptionId: "" }),
|
|
29471
|
-
unsubscribe: async () => void 0,
|
|
29472
|
-
getState: async () => null,
|
|
29473
|
-
getStatus: async () => ({
|
|
29474
|
-
brokerCount: registry.size(),
|
|
29475
|
-
connectedCount: registry.connectedCount()
|
|
29476
|
-
})
|
|
29477
|
-
};
|
|
29478
|
-
}
|
|
29479
|
-
//#endregion
|
|
29480
29317
|
//#region src/dreo-domain-mapping.ts
|
|
29481
29318
|
/** Library device-type strings we treat as a CamStack `fan` kind. */
|
|
29482
29319
|
var FAN_LIB_TYPES = new Set([
|
|
@@ -29598,11 +29435,11 @@ var DEVICES_FILTER = {
|
|
|
29598
29435
|
label: "Devices",
|
|
29599
29436
|
isDefault: true
|
|
29600
29437
|
};
|
|
29601
|
-
/** Build a `dreoDeviceId → CamStack deviceId` map for a single
|
|
29602
|
-
async function
|
|
29438
|
+
/** Build a `dreoDeviceId → CamStack deviceId` map for a single integration. */
|
|
29439
|
+
async function adoptedMapForIntegration(integrationId, listAdoptedDreo) {
|
|
29603
29440
|
const all = await listAdoptedDreo();
|
|
29604
29441
|
const map = /* @__PURE__ */ new Map();
|
|
29605
|
-
for (const device of all) if (device.config["system"] === "dreo" && device.config["brokerId"] ===
|
|
29442
|
+
for (const device of all) if (device.config["system"] === "dreo" && device.config["brokerId"] === integrationId) {
|
|
29606
29443
|
const dreoId = device.config["dreoDeviceId"];
|
|
29607
29444
|
if (typeof dreoId === "string") map.set(dreoId, device.id);
|
|
29608
29445
|
}
|
|
@@ -29610,15 +29447,15 @@ async function adoptedMapForBroker(brokerId, listAdoptedDreo) {
|
|
|
29610
29447
|
}
|
|
29611
29448
|
/**
|
|
29612
29449
|
* Construct the `device-adoption` cap provider for the Dreo addon. Pure builder:
|
|
29613
|
-
* all side-effecting deps are injected.
|
|
29614
|
-
*
|
|
29450
|
+
* all side-effecting deps are injected. Single `devices` granularity (one
|
|
29451
|
+
* Container per cloud device); resolves each account by `integrationId`.
|
|
29615
29452
|
*/
|
|
29616
29453
|
function buildDreoAdoptionProvider(deps) {
|
|
29617
|
-
const { registry,
|
|
29618
|
-
async function
|
|
29454
|
+
const { registry, hasIntegration, listIntegrations, listAdoptedDreo, adoptDevice, removeDevice, findDeviceConfig, logger } = deps;
|
|
29455
|
+
async function allCandidatesForIntegration(integrationId) {
|
|
29619
29456
|
return buildDreoCandidates({
|
|
29620
|
-
devices: registry.infos(
|
|
29621
|
-
adopted: await
|
|
29457
|
+
devices: registry.infos(integrationId),
|
|
29458
|
+
adopted: await adoptedMapForIntegration(integrationId, listAdoptedDreo)
|
|
29622
29459
|
});
|
|
29623
29460
|
}
|
|
29624
29461
|
function applyCandidateTextFilter(cands, filterText) {
|
|
@@ -29636,7 +29473,7 @@ function buildDreoAdoptionProvider(deps) {
|
|
|
29636
29473
|
return {
|
|
29637
29474
|
listCandidateFilters: async () => ({ filters: [DEVICES_FILTER] }),
|
|
29638
29475
|
listCandidates: async ({ integrationId, page, pageSize, filterText }) => {
|
|
29639
|
-
const filtered = applyCandidateTextFilter(await
|
|
29476
|
+
const filtered = applyCandidateTextFilter(await allCandidatesForIntegration(integrationId), filterText);
|
|
29640
29477
|
const start = (page - 1) * pageSize;
|
|
29641
29478
|
return {
|
|
29642
29479
|
candidates: filtered.slice(start, start + pageSize),
|
|
@@ -29646,13 +29483,12 @@ function buildDreoAdoptionProvider(deps) {
|
|
|
29646
29483
|
};
|
|
29647
29484
|
},
|
|
29648
29485
|
getCandidate: async ({ integrationId, childNativeId }) => {
|
|
29649
|
-
return (await
|
|
29486
|
+
return (await allCandidatesForIntegration(integrationId)).find((c) => c.childNativeId === childNativeId) ?? null;
|
|
29650
29487
|
},
|
|
29651
29488
|
getStatus: async () => {
|
|
29652
29489
|
try {
|
|
29653
|
-
const brokers = registry.list();
|
|
29654
29490
|
let candidateCount = 0;
|
|
29655
|
-
for (const
|
|
29491
|
+
for (const integrationId of listIntegrations()) candidateCount += (await allCandidatesForIntegration(integrationId)).length;
|
|
29656
29492
|
const adoptedCount = (await listAdoptedDreo()).filter((d) => d.config["system"] === "dreo").length;
|
|
29657
29493
|
return {
|
|
29658
29494
|
lastDiscoveryAt: Date.now(),
|
|
@@ -29671,9 +29507,8 @@ function buildDreoAdoptionProvider(deps) {
|
|
|
29671
29507
|
}
|
|
29672
29508
|
},
|
|
29673
29509
|
refresh: async ({ integrationId }) => {
|
|
29674
|
-
const
|
|
29675
|
-
const
|
|
29676
|
-
const adoptedCount = (await listAdoptedDreo()).filter((d) => d.config["system"] === "dreo" && d.config["brokerId"] === brokerId).length;
|
|
29510
|
+
const candidateCount = (await allCandidatesForIntegration(integrationId)).length;
|
|
29511
|
+
const adoptedCount = (await adoptedMapForIntegration(integrationId, listAdoptedDreo)).size;
|
|
29677
29512
|
return {
|
|
29678
29513
|
lastDiscoveryAt: Date.now(),
|
|
29679
29514
|
candidateCount,
|
|
@@ -29682,17 +29517,16 @@ function buildDreoAdoptionProvider(deps) {
|
|
|
29682
29517
|
};
|
|
29683
29518
|
},
|
|
29684
29519
|
adopt: async ({ integrationId, childNativeIds, perCandidate }) => {
|
|
29685
|
-
|
|
29686
|
-
|
|
29687
|
-
const devices = registry.infos(brokerId);
|
|
29520
|
+
if (!registry.has(integrationId)) throw new Error(`dreo adopt: integration ${integrationId} not connected`);
|
|
29521
|
+
const devices = registry.infos(integrationId);
|
|
29688
29522
|
const adopted = [];
|
|
29689
29523
|
let failures = 0;
|
|
29690
29524
|
for (const dreoDeviceId of childNativeIds) try {
|
|
29691
29525
|
const dev = devices.find((d) => d.deviceId === dreoDeviceId);
|
|
29692
29526
|
if (dev === void 0) {
|
|
29693
|
-
logger.warn("dreo adopt: device not found on
|
|
29527
|
+
logger.warn("dreo adopt: device not found on integration — skipping", { meta: {
|
|
29694
29528
|
dreoDeviceId,
|
|
29695
|
-
|
|
29529
|
+
integrationId
|
|
29696
29530
|
} });
|
|
29697
29531
|
failures++;
|
|
29698
29532
|
continue;
|
|
@@ -29704,7 +29538,7 @@ function buildDreoAdoptionProvider(deps) {
|
|
|
29704
29538
|
if (candidate === void 0) {
|
|
29705
29539
|
logger.warn("dreo adopt: device model unsupported — skipping", { meta: {
|
|
29706
29540
|
dreoDeviceId,
|
|
29707
|
-
|
|
29541
|
+
integrationId,
|
|
29708
29542
|
model: dev.model
|
|
29709
29543
|
} });
|
|
29710
29544
|
failures++;
|
|
@@ -29713,7 +29547,6 @@ function buildDreoAdoptionProvider(deps) {
|
|
|
29713
29547
|
const name = perCandidate?.[dreoDeviceId]?.name ?? dev.name ?? dreoDeviceId;
|
|
29714
29548
|
const { deviceId, accessoryDeviceIds } = await adoptDevice({
|
|
29715
29549
|
dreoDeviceId,
|
|
29716
|
-
brokerId,
|
|
29717
29550
|
integrationId,
|
|
29718
29551
|
type: candidate.type,
|
|
29719
29552
|
name,
|
|
@@ -29727,7 +29560,7 @@ function buildDreoAdoptionProvider(deps) {
|
|
|
29727
29560
|
} catch (err) {
|
|
29728
29561
|
logger.warn("dreo adopt: failed to adopt device", { meta: {
|
|
29729
29562
|
dreoDeviceId,
|
|
29730
|
-
|
|
29563
|
+
integrationId,
|
|
29731
29564
|
error: errMsg(err)
|
|
29732
29565
|
} });
|
|
29733
29566
|
failures++;
|
|
@@ -29742,9 +29575,10 @@ function buildDreoAdoptionProvider(deps) {
|
|
|
29742
29575
|
const cfg = await findDeviceConfig(camDeviceId);
|
|
29743
29576
|
if (cfg === null) throw new Error(`dreo resync: device ${camDeviceId} not found`);
|
|
29744
29577
|
if (cfg["system"] !== "dreo") throw new Error(`dreo resync: device ${camDeviceId} is not a Dreo device`);
|
|
29745
|
-
const
|
|
29578
|
+
const integrationId = String(cfg["brokerId"]);
|
|
29746
29579
|
const dreoDeviceId = String(cfg["dreoDeviceId"]);
|
|
29747
|
-
if (!
|
|
29580
|
+
if (!hasIntegration(integrationId)) throw new Error(`dreo resync: integration ${integrationId} not connected`);
|
|
29581
|
+
if (!registry.infos(integrationId).some((d) => d.deviceId === dreoDeviceId)) throw new Error(`dreo resync: device ${dreoDeviceId} no longer present on integration ${integrationId}`);
|
|
29748
29582
|
return {
|
|
29749
29583
|
changed: false,
|
|
29750
29584
|
rebuiltChildren: 0
|
|
@@ -29753,29 +29587,31 @@ function buildDreoAdoptionProvider(deps) {
|
|
|
29753
29587
|
};
|
|
29754
29588
|
}
|
|
29755
29589
|
//#endregion
|
|
29756
|
-
//#region src/dreo-
|
|
29590
|
+
//#region src/dreo-integration-device-cascade.ts
|
|
29757
29591
|
/**
|
|
29758
29592
|
* Remove every adopted Dreo PARENT device (children cascade via the kernel)
|
|
29759
|
-
* whose persisted config carries `{ system: 'dreo', brokerId }`.
|
|
29760
|
-
*
|
|
29761
|
-
*
|
|
29762
|
-
*
|
|
29593
|
+
* whose persisted config carries `{ system: 'dreo', brokerId: integrationId }`.
|
|
29594
|
+
* Used when an integration is deleted/disabled. Best-effort per device; returns
|
|
29595
|
+
* the count removed.
|
|
29596
|
+
*
|
|
29597
|
+
* NOTE: the persisted config field is still named `brokerId` (unchanged
|
|
29598
|
+
* device-side schema) but under the account model it holds the `integrationId`.
|
|
29763
29599
|
*/
|
|
29764
|
-
async function
|
|
29765
|
-
const { reg, devices, addonId,
|
|
29600
|
+
async function cascadeRemoveDevicesForIntegration(input) {
|
|
29601
|
+
const { reg, devices, addonId, integrationId, logger } = input;
|
|
29766
29602
|
let removed = 0;
|
|
29767
29603
|
for (const d of reg.getAllForAddon(addonId)) {
|
|
29768
29604
|
if (d.parentDeviceId !== null) continue;
|
|
29769
29605
|
const cfg = await devices.loadConfig(d.id).catch(() => ({}));
|
|
29770
|
-
if (cfg["system"] !== "dreo" || cfg["brokerId"] !==
|
|
29606
|
+
if (cfg["system"] !== "dreo" || cfg["brokerId"] !== integrationId) continue;
|
|
29771
29607
|
try {
|
|
29772
29608
|
await devices.remove(d.id);
|
|
29773
29609
|
removed += 1;
|
|
29774
29610
|
} catch (err) {
|
|
29775
|
-
logger.warn("dreo:
|
|
29611
|
+
logger.warn("dreo: integration cascade-remove failed", {
|
|
29776
29612
|
tags: { deviceId: d.id },
|
|
29777
29613
|
meta: {
|
|
29778
|
-
|
|
29614
|
+
integrationId,
|
|
29779
29615
|
error: errMsg(err)
|
|
29780
29616
|
}
|
|
29781
29617
|
});
|
|
@@ -29784,20 +29620,21 @@ async function cascadeRemoveDevicesForBroker(input) {
|
|
|
29784
29620
|
return removed;
|
|
29785
29621
|
}
|
|
29786
29622
|
//#endregion
|
|
29787
|
-
//#region src/dreo-
|
|
29623
|
+
//#region src/dreo-integration-offline.ts
|
|
29788
29624
|
/**
|
|
29789
|
-
* Set every Dreo device belonging to `
|
|
29790
|
-
* `devices` is the addon-scoped device list; every Dreo top-level
|
|
29791
|
-
* carries `{ system: 'dreo', brokerId }` in its config
|
|
29625
|
+
* Set every Dreo device belonging to `integrationId` to the requested online
|
|
29626
|
+
* state. `devices` is the addon-scoped device list; every Dreo top-level
|
|
29627
|
+
* Container carries `{ system: 'dreo', brokerId: integrationId }` in its config
|
|
29628
|
+
* blob (the `brokerId` field name is unchanged; it now holds the integrationId).
|
|
29792
29629
|
*
|
|
29793
29630
|
* Churn-free: skips any device already in the target state. Returns the count of
|
|
29794
|
-
* devices actually transitioned.
|
|
29631
|
+
* devices actually transitioned.
|
|
29795
29632
|
*/
|
|
29796
|
-
function
|
|
29633
|
+
function setIntegrationDevicesOnline(devices, integrationId, online) {
|
|
29797
29634
|
let count = 0;
|
|
29798
29635
|
for (const dev of devices) {
|
|
29799
29636
|
if (dev.config.get("system") !== "dreo") continue;
|
|
29800
|
-
if (dev.config.get("brokerId") !==
|
|
29637
|
+
if (dev.config.get("brokerId") !== integrationId) continue;
|
|
29801
29638
|
if (dev.online === online) continue;
|
|
29802
29639
|
dev.markOnline(online);
|
|
29803
29640
|
count += 1;
|
|
@@ -30343,199 +30180,137 @@ var DreoContainerDevice = class extends BaseDevice$1 {
|
|
|
30343
30180
|
};
|
|
30344
30181
|
//#endregion
|
|
30345
30182
|
//#region src/addon.ts
|
|
30346
|
-
/** Default multi-broker config — a fresh install starts with no accounts. */
|
|
30347
|
-
var DEFAULTS = { brokers: [] };
|
|
30348
30183
|
/**
|
|
30349
|
-
* Dreo device-provider addon (multi-account).
|
|
30184
|
+
* Dreo device-provider addon — `mode: account` (multi-account, broker-less).
|
|
30350
30185
|
*
|
|
30351
|
-
* Wraps the `@apocaliss92/nodedreo` Dreo cloud client (REST + WebSocket).
|
|
30352
|
-
*
|
|
30353
|
-
*
|
|
30354
|
-
*
|
|
30355
|
-
*
|
|
30356
|
-
*
|
|
30357
|
-
*
|
|
30186
|
+
* Wraps the `@apocaliss92/nodedreo` Dreo cloud client (REST + WebSocket). Each
|
|
30187
|
+
* Dreo integration carries its own account credentials in its
|
|
30188
|
+
* `integration.settings`; the addon holds one {@link DreoIntegrationManager} per
|
|
30189
|
+
* `integrationId` in {@link DreoClientRegistry} (`Map<integrationId, manager>`).
|
|
30190
|
+
* There is NO shared broker. The live connection surface each manager owns
|
|
30191
|
+
* (discovery info + per-device handles) is published on the in-process
|
|
30192
|
+
* {@link dreoConnections} resolver KEYED BY `integrationId`.
|
|
30193
|
+
*
|
|
30194
|
+
* A `device-adoption` cap provider enumerates each account's cloud devices and
|
|
30195
|
+
* adopts them; each adopted cloud device becomes a {@link DeviceType.Container}
|
|
30196
|
+
* parent that fans out a single typed accessory child (fan / purifier / heater /
|
|
30197
|
+
* humidifier) and its entity children. The device classes resolve their live
|
|
30198
|
+
* handle by the `brokerId` config field — UNCHANGED device-side code; that field
|
|
30199
|
+
* now holds the integrationId (see the migration note in the addon docs).
|
|
30358
30200
|
*
|
|
30359
30201
|
* Placement: hub-only — the Dreo cloud is reached over outbound HTTPS + a
|
|
30360
30202
|
* persistent WebSocket from a single account session; no LAN proximity matters,
|
|
30361
|
-
* so (like Dreame) the
|
|
30203
|
+
* so (like Dreame) the connection lives on the hub.
|
|
30362
30204
|
*/
|
|
30363
30205
|
var DreoProviderAddon = class extends BaseDeviceProvider {
|
|
30364
30206
|
addonId = "provider-dreo";
|
|
30365
30207
|
providerName = "Dreo";
|
|
30366
30208
|
deviceClasses = { [DeviceType.Container]: DreoContainerDevice };
|
|
30367
|
-
|
|
30209
|
+
clients = null;
|
|
30368
30210
|
constructor() {
|
|
30369
|
-
super({
|
|
30211
|
+
super({});
|
|
30370
30212
|
}
|
|
30371
30213
|
async onInitialize() {
|
|
30372
30214
|
const regs = await super.onInitialize();
|
|
30373
|
-
this.
|
|
30374
|
-
this.
|
|
30375
|
-
this.ctx.logger.info("Dreo:
|
|
30376
|
-
this.
|
|
30215
|
+
this.clients = new DreoClientRegistry(this.ctx.logger);
|
|
30216
|
+
this.clients.setOnConnected((integrationId) => {
|
|
30217
|
+
this.ctx.logger.info("Dreo: integration connected", { meta: { integrationId } });
|
|
30218
|
+
this.setIntegrationDevicesOnline(integrationId, true);
|
|
30377
30219
|
});
|
|
30378
|
-
this.
|
|
30379
|
-
this.
|
|
30220
|
+
this.clients.setOnDisconnected((integrationId) => {
|
|
30221
|
+
this.setIntegrationDevicesOnline(integrationId, false);
|
|
30380
30222
|
});
|
|
30381
|
-
await this.
|
|
30382
|
-
this.ctx.logger.info("Dreo: provider initialised", { meta: { brokerCount: this.config.brokers.length } });
|
|
30383
|
-
await this.reconcileIntegrationsToBrokers();
|
|
30223
|
+
await this.reconcileIntegrations();
|
|
30384
30224
|
this.subscribeIntegrationLifecycle();
|
|
30385
|
-
|
|
30386
|
-
|
|
30387
|
-
|
|
30388
|
-
|
|
30389
|
-
|
|
30390
|
-
},
|
|
30391
|
-
{
|
|
30392
|
-
capability: deviceAdoptionCapability,
|
|
30393
|
-
provider: this.buildAdoptionProvider()
|
|
30394
|
-
}
|
|
30395
|
-
];
|
|
30396
|
-
}
|
|
30397
|
-
/** Reconcile the live registry against the persisted `brokers` array after a
|
|
30398
|
-
* settings write (UI save or `broker.*` cap). Mirrors the Dreame addon. */
|
|
30399
|
-
async onConfigChanged() {
|
|
30400
|
-
const reg = this.registry;
|
|
30401
|
-
if (!reg) return;
|
|
30402
|
-
const persisted = this.config.brokers;
|
|
30403
|
-
const liveIds = new Set(reg.list().map((b) => b.id));
|
|
30404
|
-
const persistedIds = new Set(persisted.map((e) => e.id));
|
|
30405
|
-
for (const liveId of liveIds) if (!persistedIds.has(liveId)) try {
|
|
30406
|
-
await reg.removeEntry(liveId);
|
|
30407
|
-
} catch (err) {
|
|
30408
|
-
this.ctx.logger.warn("Dreo onConfigChanged: removeEntry failed", { meta: {
|
|
30409
|
-
brokerId: liveId,
|
|
30410
|
-
error: errMsg(err)
|
|
30411
|
-
} });
|
|
30412
|
-
}
|
|
30413
|
-
for (const entry of persisted) {
|
|
30414
|
-
if (entry.id && liveIds.has(entry.id)) {
|
|
30415
|
-
if (sameConnection(entry.connection, reg.getConnection(entry.id))) continue;
|
|
30416
|
-
try {
|
|
30417
|
-
await reg.updateEntry(entry.id, entry.connection);
|
|
30418
|
-
} catch (err) {
|
|
30419
|
-
this.ctx.logger.warn("Dreo onConfigChanged: updateEntry failed", { meta: {
|
|
30420
|
-
brokerId: entry.id,
|
|
30421
|
-
error: errMsg(err)
|
|
30422
|
-
} });
|
|
30423
|
-
}
|
|
30424
|
-
continue;
|
|
30425
|
-
}
|
|
30426
|
-
try {
|
|
30427
|
-
const created = await reg.createEntry(entry.name, entry.connection);
|
|
30428
|
-
if (created.id !== entry.id) {
|
|
30429
|
-
const next = persisted.map((b) => b === entry ? {
|
|
30430
|
-
...b,
|
|
30431
|
-
id: created.id
|
|
30432
|
-
} : b);
|
|
30433
|
-
await this.updateGlobalSettings({ brokers: next });
|
|
30434
|
-
}
|
|
30435
|
-
} catch (err) {
|
|
30436
|
-
this.ctx.logger.warn("Dreo onConfigChanged: failed to start new broker", { meta: {
|
|
30437
|
-
brokerName: entry.name,
|
|
30438
|
-
error: errMsg(err)
|
|
30439
|
-
} });
|
|
30440
|
-
}
|
|
30441
|
-
}
|
|
30225
|
+
this.ctx.logger.info("Dreo: provider initialised", { meta: { integrationCount: this.requireClients().size() } });
|
|
30226
|
+
return [...regs, {
|
|
30227
|
+
capability: deviceAdoptionCapability,
|
|
30228
|
+
provider: this.buildAdoptionProvider()
|
|
30229
|
+
}];
|
|
30442
30230
|
}
|
|
30443
30231
|
async onShutdown() {
|
|
30444
30232
|
try {
|
|
30445
|
-
await this.
|
|
30233
|
+
await this.clients?.shutdown();
|
|
30446
30234
|
} catch (err) {
|
|
30447
30235
|
this.ctx.logger.warn("Dreo: provider shutdown error", { meta: { error: errMsg(err) } });
|
|
30448
30236
|
}
|
|
30449
|
-
this.
|
|
30237
|
+
this.clients = null;
|
|
30450
30238
|
await super.onShutdown();
|
|
30451
30239
|
}
|
|
30452
|
-
|
|
30453
|
-
|
|
30240
|
+
requireClients() {
|
|
30241
|
+
if (!this.clients) throw new Error("Dreo provider not initialised");
|
|
30242
|
+
return this.clients;
|
|
30243
|
+
}
|
|
30244
|
+
/**
|
|
30245
|
+
* Stable id, integration-scoped so two accounts exposing the same cloud device
|
|
30246
|
+
* id don't collide. `brokerId` in the config is the integrationId (unchanged
|
|
30247
|
+
* field name).
|
|
30248
|
+
*/
|
|
30454
30249
|
generateStableId(_type, config) {
|
|
30455
30250
|
return `dreo:${String(config?.["brokerId"] ?? "unknown")}:${String(config?.["dreoDeviceId"] ?? Date.now())}`;
|
|
30456
30251
|
}
|
|
30457
|
-
buildBrokerProvider() {
|
|
30458
|
-
return buildDreoBrokerProvider({
|
|
30459
|
-
ownerAddonId: this.ctx.id,
|
|
30460
|
-
registry: this.requireRegistry(),
|
|
30461
|
-
getBrokers: () => this.config.brokers,
|
|
30462
|
-
persistBrokers: (brokers) => this.updateGlobalSettings({ brokers: [...brokers] }),
|
|
30463
|
-
cascadeRemoveDevices: (brokerId) => this.cascadeRemoveDevicesForBroker(brokerId),
|
|
30464
|
-
logger: this.ctx.logger
|
|
30465
|
-
});
|
|
30466
|
-
}
|
|
30467
|
-
async cascadeRemoveDevicesForBroker(brokerId) {
|
|
30468
|
-
const reg = this.ctx.kernel.deviceRegistry;
|
|
30469
|
-
const devices = this.ctx.kernel.devices;
|
|
30470
|
-
if (!reg || !devices) return;
|
|
30471
|
-
await cascadeRemoveDevicesForBroker({
|
|
30472
|
-
reg,
|
|
30473
|
-
devices,
|
|
30474
|
-
addonId: this.addonId,
|
|
30475
|
-
brokerId,
|
|
30476
|
-
logger: this.ctx.logger
|
|
30477
|
-
});
|
|
30478
|
-
}
|
|
30479
30252
|
/**
|
|
30480
|
-
*
|
|
30481
|
-
*
|
|
30482
|
-
*
|
|
30483
|
-
* was deleted
|
|
30484
|
-
*
|
|
30253
|
+
* Rebuild the `Map<integrationId, manager>` from the live integration list:
|
|
30254
|
+
* for each surviving Dreo integration read its settings and upsert a manager
|
|
30255
|
+
* (session-preserving if credentials are unchanged); drop managers whose
|
|
30256
|
+
* integration was deleted/disabled — cascade-removing their adopted devices.
|
|
30257
|
+
* Idempotent; runs on boot + on every integration lifecycle event. Guarded so
|
|
30258
|
+
* a failure never fails init.
|
|
30485
30259
|
*/
|
|
30486
|
-
async
|
|
30260
|
+
async reconcileIntegrations() {
|
|
30261
|
+
const reg = this.requireClients();
|
|
30487
30262
|
try {
|
|
30488
|
-
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
|
|
30489
|
-
const surviving = new Set(
|
|
30490
|
-
for (const integration of mine) {
|
|
30491
|
-
const
|
|
30492
|
-
|
|
30493
|
-
|
|
30494
|
-
if (!this.config.brokers.some((b) => b.id === brokerId)) {
|
|
30495
|
-
this.ctx.logger.warn("Dreo integration→broker: linked broker not found", { meta: {
|
|
30496
|
-
integrationId: integration.id,
|
|
30497
|
-
brokerId
|
|
30498
|
-
} });
|
|
30263
|
+
const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id && i.enabled);
|
|
30264
|
+
const surviving = /* @__PURE__ */ new Set();
|
|
30265
|
+
for (const integration of mine) try {
|
|
30266
|
+
const connection = connectionFromSettings(await this.ctx.api.integrations.getSettings.query({ id: integration.id }));
|
|
30267
|
+
if (!connection) {
|
|
30268
|
+
this.ctx.logger.warn("Dreo integration has no complete credentials — skipping", { meta: { integrationId: integration.id } });
|
|
30499
30269
|
continue;
|
|
30500
30270
|
}
|
|
30501
|
-
|
|
30502
|
-
|
|
30503
|
-
const toRemove = this.config.brokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
|
|
30504
|
-
if (toRemove.length === 0) return;
|
|
30505
|
-
for (const id of toRemove) try {
|
|
30506
|
-
await this.requireRegistry().removeEntry(id);
|
|
30507
|
-
await this.cascadeRemoveDevicesForBroker(id);
|
|
30271
|
+
await reg.upsert(integration.id, integration.name, connection);
|
|
30272
|
+
surviving.add(integration.id);
|
|
30508
30273
|
} catch (err) {
|
|
30509
|
-
this.ctx.logger.warn("Dreo
|
|
30510
|
-
|
|
30274
|
+
this.ctx.logger.warn("Dreo reconcile: failed to read integration settings", { meta: {
|
|
30275
|
+
integrationId: integration.id,
|
|
30511
30276
|
error: errMsg(err)
|
|
30512
30277
|
} });
|
|
30513
30278
|
}
|
|
30514
|
-
const
|
|
30515
|
-
await
|
|
30279
|
+
const vanished = reg.list().filter((id) => !surviving.has(id));
|
|
30280
|
+
await reg.retain(surviving);
|
|
30281
|
+
for (const integrationId of vanished) await this.cascadeRemoveDevicesForIntegration(integrationId);
|
|
30516
30282
|
} catch (err) {
|
|
30517
|
-
this.ctx.logger.warn("Dreo integration
|
|
30283
|
+
this.ctx.logger.warn("Dreo integration reconcile failed", { meta: { error: errMsg(err) } });
|
|
30518
30284
|
}
|
|
30519
30285
|
}
|
|
30520
30286
|
subscribeIntegrationLifecycle() {
|
|
30521
30287
|
const handler = (event) => {
|
|
30522
30288
|
const addonId = event.data["addonId"];
|
|
30523
30289
|
if (typeof addonId === "string" && addonId !== this.ctx.id) return;
|
|
30524
|
-
this.
|
|
30290
|
+
this.reconcileIntegrations();
|
|
30525
30291
|
};
|
|
30526
30292
|
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationEnabled }, handler);
|
|
30527
30293
|
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDisabled }, handler);
|
|
30528
30294
|
this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDeleted }, handler);
|
|
30529
30295
|
}
|
|
30296
|
+
async cascadeRemoveDevicesForIntegration(integrationId) {
|
|
30297
|
+
const reg = this.ctx.kernel.deviceRegistry;
|
|
30298
|
+
const devices = this.ctx.kernel.devices;
|
|
30299
|
+
if (!reg || !devices) return;
|
|
30300
|
+
await cascadeRemoveDevicesForIntegration({
|
|
30301
|
+
reg,
|
|
30302
|
+
devices,
|
|
30303
|
+
addonId: this.addonId,
|
|
30304
|
+
integrationId,
|
|
30305
|
+
logger: this.ctx.logger
|
|
30306
|
+
});
|
|
30307
|
+
}
|
|
30530
30308
|
buildAdoptionProvider() {
|
|
30531
30309
|
return buildDreoAdoptionProvider({
|
|
30532
30310
|
registry: dreoConnections,
|
|
30533
30311
|
logger: this.ctx.logger,
|
|
30534
|
-
|
|
30535
|
-
|
|
30536
|
-
if (typeof brokerId !== "string") throw new Error(`integration ${id} has no linked brokerId`);
|
|
30537
|
-
return brokerId;
|
|
30538
|
-
},
|
|
30312
|
+
hasIntegration: (integrationId) => this.requireClients().has(integrationId),
|
|
30313
|
+
listIntegrations: () => this.requireClients().list(),
|
|
30539
30314
|
listAdoptedDreo: async () => {
|
|
30540
30315
|
const reg = this.ctx.kernel.deviceRegistry;
|
|
30541
30316
|
const devices = this.ctx.kernel.devices;
|
|
@@ -30551,12 +30326,12 @@ var DreoProviderAddon = class extends BaseDeviceProvider {
|
|
|
30551
30326
|
}
|
|
30552
30327
|
return out;
|
|
30553
30328
|
},
|
|
30554
|
-
adoptDevice: async ({ dreoDeviceId,
|
|
30329
|
+
adoptDevice: async ({ dreoDeviceId, integrationId, name, model }) => {
|
|
30555
30330
|
const devices = this.ctx.kernel.devices;
|
|
30556
30331
|
if (!devices) throw new Error("dreo adopt: kernel.devices unavailable");
|
|
30557
30332
|
const config = {
|
|
30558
30333
|
dreoDeviceId,
|
|
30559
|
-
brokerId,
|
|
30334
|
+
brokerId: integrationId,
|
|
30560
30335
|
model,
|
|
30561
30336
|
system: "dreo",
|
|
30562
30337
|
integrationId,
|
|
@@ -30585,18 +30360,7 @@ var DreoProviderAddon = class extends BaseDeviceProvider {
|
|
|
30585
30360
|
});
|
|
30586
30361
|
}
|
|
30587
30362
|
globalSettingsSchema() {
|
|
30588
|
-
return
|
|
30589
|
-
id: "dreo-broker",
|
|
30590
|
-
title: "Dreo account",
|
|
30591
|
-
description: "Dreo accounts are managed in the External systems → Brokers tab.",
|
|
30592
|
-
columns: 1,
|
|
30593
|
-
fields: [{
|
|
30594
|
-
type: "info",
|
|
30595
|
-
key: "brokerHelp",
|
|
30596
|
-
label: "Accounts are managed separately",
|
|
30597
|
-
content: "This integration links to a Dreo account broker. Add, edit, or remove accounts from External systems → Brokers. The integration only stores a reference to its broker — credentials live on the broker."
|
|
30598
|
-
}]
|
|
30599
|
-
}] });
|
|
30363
|
+
return buildConnectionFormSchema();
|
|
30600
30364
|
}
|
|
30601
30365
|
async supportsManualCreation() {
|
|
30602
30366
|
return false;
|
|
@@ -30607,19 +30371,15 @@ var DreoProviderAddon = class extends BaseDeviceProvider {
|
|
|
30607
30371
|
async onCreateDevice(_type, _config) {
|
|
30608
30372
|
throw new Error("Dreo devices are adopted from the cloud, not created manually");
|
|
30609
30373
|
}
|
|
30610
|
-
|
|
30374
|
+
setIntegrationDevicesOnline(integrationId, online) {
|
|
30611
30375
|
const reg = this.ctx.kernel.deviceRegistry;
|
|
30612
30376
|
if (!reg) return;
|
|
30613
|
-
const n =
|
|
30614
|
-
if (n > 0) this.ctx.logger.info("Dreo:
|
|
30615
|
-
|
|
30377
|
+
const n = setIntegrationDevicesOnline(reg.getAllForAddon(this.addonId), integrationId, online);
|
|
30378
|
+
if (n > 0) this.ctx.logger.info("Dreo: integration devices " + (online ? "online" : "offline"), { meta: {
|
|
30379
|
+
integrationId,
|
|
30616
30380
|
count: n
|
|
30617
30381
|
} });
|
|
30618
30382
|
}
|
|
30619
|
-
requireRegistry() {
|
|
30620
|
-
if (!this.registry) throw new Error("Dreo provider not initialised");
|
|
30621
|
-
return this.registry;
|
|
30622
|
-
}
|
|
30623
30383
|
};
|
|
30624
30384
|
//#endregion
|
|
30625
30385
|
exports.DREO_MAX_FAN_LEVEL = DREO_MAX_FAN_LEVEL;
|
|
@@ -30630,8 +30390,8 @@ exports.buildDreoCandidates = buildDreoCandidates;
|
|
|
30630
30390
|
exports.capsForKind = capsForKind;
|
|
30631
30391
|
exports.clampPercent = clampPercent;
|
|
30632
30392
|
exports.classifyDreoModel = classifyDreoModel;
|
|
30393
|
+
exports.connectionFromSettings = connectionFromSettings;
|
|
30633
30394
|
exports.deviceTypeForKind = deviceTypeForKind;
|
|
30634
|
-
exports.dreoAddonConfigSchema = dreoAddonConfigSchema;
|
|
30635
30395
|
exports.dreoConfigSchema = dreoConfigSchema;
|
|
30636
30396
|
exports.levelToPercentage = levelToPercentage;
|
|
30637
30397
|
exports.percentageStep = percentageStep;
|