@camstack/addon-provider-homeassistant 1.2.172 → 1.2.601
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 +210 -148
- package/dist/addon.mjs +192 -148
- package/dist/{dist-B9xf8_Ix.mjs → dist-BQLjEZxA.mjs} +1373 -125
- package/dist/{dist-Ba9raKow.js → dist-BXMUyWXo.js} +1402 -124
- package/dist/ha-export/ha-export.addon.js +1 -1
- package/dist/ha-export/ha-export.addon.mjs +1 -1
- package/package.json +1 -1
package/dist/addon.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as connectivityCapability, At as
|
|
1
|
+
import { A as connectivityCapability, At as boolean, B as floodCapability, Bt as EventCategory, C as buildAddonRouteProvider, Ct as defineEntryKind, D as carbonMonoxideCapability, Dt as settingsBrokersAdoptsFrom, E as calendarSourceCapability, F as deviceAdoptionCapability, Ft as record, G as lawnMowerControlCapability, H as humidifierCapability, It as string, J as motionCapability, K as lockControlCapability, L as enumSensorCapability, Lt as union, M as controlCapability, Mt as literal, N as coverCapability, Nt as number, O as climateControlCapability, Ot as _enum, P as deviceAdminLinkCapability, Pt as object, Q as numericSensorCapability, R as eventEmitterCapability, Rt as unknown, S as brokerCapability, St as createEvent, T as buttonCapability, Tt as entryBody, U as humiditySensorCapability, V as gasCapability, W as imageCapability, X as notificationOutputCapability, Y as normalizeUnit, Z as notifierCapability, _ as automationControlCapability, _t as DeviceRole, at as scriptRunnerCapability, b as binaryCapability, ct as tamperCapability, d as TargetSchema, dt as vacuumControlCapability, et as parseAdminBase, f as accessoriesCapability, ft as valveCapability, g as ambientLightSensorCapability, gt as DeviceFeature, h as alarmPanelCapability, ht as weatherCapability, it as pressureSensorCapability, j as contactCapability, jt as discriminatedUnion, k as colorCapability, kt as array, lt as temperatureSensorCapability, m as airQualitySensorCapability, mt as waterHeaterCapability, n as BaseDevice, nt as prepareNotification, ot as smokeCapability, p as addonRoutesCapability, pt as vibrationCapability, q as mediaPlayerCapability, r as BaseDeviceProvider, rt as presenceCapability, s as EnumSensorDateTimeFormatSchema, st as switchCapability, tt as powerMeterCapability, ut as updateCapability, v as batteryCapability, vt as DeviceType, w as buildDeviceAdminUrl, wt as defineRecordKinds, x as brightnessCapability, xt as RecordSecretUnavailableError, y as bestLocationMatch, yt as errMsg, z as fanControlCapability, zt as url } from "./dist-BQLjEZxA.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
|
|
4
4
|
//#region src/ha-admin-link.ts
|
|
@@ -1591,6 +1591,7 @@ var COLD_START$15 = {
|
|
|
1591
1591
|
* - `target_temp_high` / `target_temp_low` → `targetHigh` / `targetLow`
|
|
1592
1592
|
* - `target_humidity` → `targetHumidity`
|
|
1593
1593
|
* - `current_temperature` / `current_humidity` → read-only
|
|
1594
|
+
* - `min_temp` / `max_temp` / `target_temp_step` → setpoint bounds
|
|
1594
1595
|
*
|
|
1595
1596
|
* Mutations route to:
|
|
1596
1597
|
* - `climate.set_hvac_mode({ hvac_mode })`
|
|
@@ -1690,6 +1691,9 @@ function climateSliceFromState(state) {
|
|
|
1690
1691
|
currentTemp: readNumberAttr$5(attrs, "current_temperature"),
|
|
1691
1692
|
currentHumidity: readNumberAttr$5(attrs, "current_humidity"),
|
|
1692
1693
|
...readSwing(attrs),
|
|
1694
|
+
minTemp: readNumberAttr$5(attrs, "min_temp"),
|
|
1695
|
+
maxTemp: readNumberAttr$5(attrs, "max_temp"),
|
|
1696
|
+
tempStep: readNumberAttr$5(attrs, "target_temp_step"),
|
|
1693
1697
|
lastFetchedAt
|
|
1694
1698
|
};
|
|
1695
1699
|
}
|
|
@@ -5804,7 +5808,7 @@ function mapHaEntityToCamStack(input) {
|
|
|
5804
5808
|
switch (domain) {
|
|
5805
5809
|
case "switch": return {
|
|
5806
5810
|
deviceType: DeviceType.Switch,
|
|
5807
|
-
role: null,
|
|
5811
|
+
role: deviceClass === "outlet" ? DeviceRole.Outlet : null,
|
|
5808
5812
|
capabilities: ["switch"]
|
|
5809
5813
|
};
|
|
5810
5814
|
case "input_boolean": return {
|
|
@@ -6777,6 +6781,36 @@ function createHaIconRouteProvider() {
|
|
|
6777
6781
|
}
|
|
6778
6782
|
}]);
|
|
6779
6783
|
}
|
|
6784
|
+
var haBrokersEntryKind = defineEntryKind("brokers", {
|
|
6785
|
+
title: "Home Assistant brokers",
|
|
6786
|
+
schema: entryBody({
|
|
6787
|
+
auth: HaBrokerEntrySchema.shape.auth,
|
|
6788
|
+
integrationId: string().optional()
|
|
6789
|
+
}),
|
|
6790
|
+
writeTrigger: "an operator adds, edits or removes a Home Assistant broker connection",
|
|
6791
|
+
adoptsFrom: settingsBrokersAdoptsFrom("provider-homeassistant")
|
|
6792
|
+
});
|
|
6793
|
+
var haRecordKinds = defineRecordKinds({
|
|
6794
|
+
/**
|
|
6795
|
+
* One row per configured Home Assistant push target. Operator-managed via the
|
|
6796
|
+
* notification-output cap CRUD; no TTL.
|
|
6797
|
+
* The legacy collection name is `provider-homeassistant:ha-notification-targets`.
|
|
6798
|
+
*/
|
|
6799
|
+
"ha-notification-targets": {
|
|
6800
|
+
schema: TargetSchema.omit({ id: true }),
|
|
6801
|
+
class: "config",
|
|
6802
|
+
writeTrigger: "an operator creates, updates, enables/disables or deletes a HA notification target",
|
|
6803
|
+
device: "none",
|
|
6804
|
+
at: "none",
|
|
6805
|
+
retention: { none: "a row is removed only on an explicit operator delete; bounded by the number of HA notify targets the operator configures" },
|
|
6806
|
+
adoptsFrom: "provider-homeassistant:ha-notification-targets",
|
|
6807
|
+
entry: { title: "Home Assistant notification targets" }
|
|
6808
|
+
} });
|
|
6809
|
+
/** All record kinds this addon declares (targets + B2 broker entries). */
|
|
6810
|
+
var haAllRecordKinds = {
|
|
6811
|
+
...haRecordKinds,
|
|
6812
|
+
[haBrokersEntryKind.name]: haBrokersEntryKind
|
|
6813
|
+
};
|
|
6780
6814
|
//#endregion
|
|
6781
6815
|
//#region src/ha-notification-output.ts
|
|
6782
6816
|
/**
|
|
@@ -6811,8 +6845,6 @@ function createHaIconRouteProvider() {
|
|
|
6811
6845
|
*/
|
|
6812
6846
|
/** The single kind this provider contributes to the shared catalog. */
|
|
6813
6847
|
var HA_NOTIFICATION_KIND = "homeassistant";
|
|
6814
|
-
/** addonId-namespaced settings-store collection holding HA notify targets. */
|
|
6815
|
-
var HA_TARGETS_COLLECTION = "provider-homeassistant:ha-notification-targets";
|
|
6816
6848
|
function haConfigSchema() {
|
|
6817
6849
|
return { sections: [{
|
|
6818
6850
|
id: "connection",
|
|
@@ -6992,71 +7024,59 @@ function parseNotifyServices(result, brokerId) {
|
|
|
6992
7024
|
}
|
|
6993
7025
|
}));
|
|
6994
7026
|
}
|
|
6995
|
-
/**
|
|
6996
|
-
*
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
return parsed.success ? parsed.data : null;
|
|
7008
|
-
}
|
|
7009
|
-
function targetToData(target) {
|
|
7010
|
-
return {
|
|
7011
|
-
name: target.name,
|
|
7012
|
-
kind: target.kind,
|
|
7013
|
-
addonId: target.addonId,
|
|
7014
|
-
enabled: target.enabled,
|
|
7015
|
-
config: target.config
|
|
7016
|
-
};
|
|
7017
|
-
}
|
|
7018
|
-
/** CRUD over one settings-store collection of `homeassistant` notify targets. */
|
|
7019
|
-
var HaTargetStore = class {
|
|
7020
|
-
store;
|
|
7021
|
-
collection;
|
|
7022
|
-
constructor(store, collection = HA_TARGETS_COLLECTION) {
|
|
7023
|
-
this.store = store;
|
|
7024
|
-
this.collection = collection;
|
|
7027
|
+
/**
|
|
7028
|
+
* CRUD over the `ha-notification-targets` record kind.
|
|
7029
|
+
*
|
|
7030
|
+
* `HaTargetStore.of(records)` is the production factory; the test helper
|
|
7031
|
+
* (`makeFakeHaRecords`) supplies a fake transport so nothing hits SQLite.
|
|
7032
|
+
* No `init()` method: kinds are declared at addon boot via
|
|
7033
|
+
* `ctx.records.declare(haRecordKinds)`.
|
|
7034
|
+
*/
|
|
7035
|
+
var HaTargetStore = class HaTargetStore {
|
|
7036
|
+
kind;
|
|
7037
|
+
constructor(kind) {
|
|
7038
|
+
this.kind = kind;
|
|
7025
7039
|
}
|
|
7026
|
-
/**
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
async init() {
|
|
7030
|
-
await this.store.declareCollection(this.collection, KV_BLOB_COLUMNS);
|
|
7040
|
+
/** Factory: build from a declared `RecordsAccess` (after `ctx.records.declare`). */
|
|
7041
|
+
static of(records) {
|
|
7042
|
+
return new HaTargetStore(records.kind(haRecordKinds["ha-notification-targets"]));
|
|
7031
7043
|
}
|
|
7032
7044
|
async list() {
|
|
7033
|
-
const
|
|
7045
|
+
const page = await this.kind.query({ limit: 1e3 });
|
|
7034
7046
|
const out = [];
|
|
7035
|
-
for (const
|
|
7036
|
-
const
|
|
7037
|
-
|
|
7047
|
+
for (const record of page.records) {
|
|
7048
|
+
const parsed = TargetSchema.safeParse({
|
|
7049
|
+
id: record.id,
|
|
7050
|
+
...record.data
|
|
7051
|
+
});
|
|
7052
|
+
if (parsed.success) out.push(parsed.data);
|
|
7038
7053
|
}
|
|
7039
7054
|
return out;
|
|
7040
7055
|
}
|
|
7041
7056
|
async getById(id) {
|
|
7042
|
-
const row =
|
|
7043
|
-
|
|
7057
|
+
const row = await this.kind.get(id);
|
|
7058
|
+
if (row === null) return null;
|
|
7059
|
+
const parsed = TargetSchema.safeParse({
|
|
7060
|
+
id: row.id,
|
|
7061
|
+
...row.data
|
|
7062
|
+
});
|
|
7063
|
+
return parsed.success ? parsed.data : null;
|
|
7044
7064
|
}
|
|
7045
7065
|
async upsert(target) {
|
|
7046
|
-
const
|
|
7047
|
-
const
|
|
7048
|
-
if (
|
|
7049
|
-
|
|
7050
|
-
id
|
|
7051
|
-
data
|
|
7066
|
+
const { id, ...rest } = target;
|
|
7067
|
+
const parsed = haRecordKinds["ha-notification-targets"].schema.safeParse(rest);
|
|
7068
|
+
if (!parsed.success) throw new Error(`invalid HA target '${id}': ${parsed.error.message}`);
|
|
7069
|
+
await this.kind.put({
|
|
7070
|
+
id,
|
|
7071
|
+
data: parsed.data
|
|
7052
7072
|
});
|
|
7053
7073
|
}
|
|
7054
7074
|
async delete(id) {
|
|
7055
|
-
await this.
|
|
7075
|
+
await this.kind.delete(id);
|
|
7056
7076
|
}
|
|
7057
7077
|
async setEnabled(id, enabled) {
|
|
7058
7078
|
const target = await this.getById(id);
|
|
7059
|
-
if (
|
|
7079
|
+
if (target === null) return null;
|
|
7060
7080
|
const next = {
|
|
7061
7081
|
...target,
|
|
7062
7082
|
enabled
|
|
@@ -7065,78 +7085,6 @@ var HaTargetStore = class {
|
|
|
7065
7085
|
return next;
|
|
7066
7086
|
}
|
|
7067
7087
|
};
|
|
7068
|
-
/** Adapt the tRPC-shaped `settings-store` cap client to the narrow port. */
|
|
7069
|
-
function createApiHaSettingsStorePort(api) {
|
|
7070
|
-
return {
|
|
7071
|
-
query: async (collection) => {
|
|
7072
|
-
return (await api.settingsStore.query.query({ collection })).map((r) => ({
|
|
7073
|
-
id: r.id,
|
|
7074
|
-
data: r.data
|
|
7075
|
-
}));
|
|
7076
|
-
},
|
|
7077
|
-
insert: async (collection, record) => {
|
|
7078
|
-
await api.settingsStore.insert.mutate({
|
|
7079
|
-
collection,
|
|
7080
|
-
record: {
|
|
7081
|
-
id: record.id,
|
|
7082
|
-
data: { ...record.data }
|
|
7083
|
-
}
|
|
7084
|
-
});
|
|
7085
|
-
},
|
|
7086
|
-
update: async (collection, id, data) => {
|
|
7087
|
-
await api.settingsStore.update.mutate({
|
|
7088
|
-
collection,
|
|
7089
|
-
id,
|
|
7090
|
-
data
|
|
7091
|
-
});
|
|
7092
|
-
},
|
|
7093
|
-
remove: async (collection, id) => {
|
|
7094
|
-
await api.settingsStore.delete.mutate({
|
|
7095
|
-
collection,
|
|
7096
|
-
key: id
|
|
7097
|
-
});
|
|
7098
|
-
},
|
|
7099
|
-
declareCollection: async (collection, columns) => {
|
|
7100
|
-
await api.settingsStore.declareCollection.mutate({
|
|
7101
|
-
collection,
|
|
7102
|
-
columns: columns.map((c) => ({ ...c }))
|
|
7103
|
-
});
|
|
7104
|
-
}
|
|
7105
|
-
};
|
|
7106
|
-
}
|
|
7107
|
-
/** In-memory port — a resilient fallback when no `api` is wired. */
|
|
7108
|
-
function createMemoryHaSettingsStorePort() {
|
|
7109
|
-
const collections = /* @__PURE__ */ new Map();
|
|
7110
|
-
const bucket = (collection) => {
|
|
7111
|
-
let map = collections.get(collection);
|
|
7112
|
-
if (!map) {
|
|
7113
|
-
map = /* @__PURE__ */ new Map();
|
|
7114
|
-
collections.set(collection, map);
|
|
7115
|
-
}
|
|
7116
|
-
return map;
|
|
7117
|
-
};
|
|
7118
|
-
return {
|
|
7119
|
-
query: async (collection) => [...bucket(collection).values()],
|
|
7120
|
-
insert: async (collection, record) => {
|
|
7121
|
-
bucket(collection).set(record.id, {
|
|
7122
|
-
id: record.id,
|
|
7123
|
-
data: { ...record.data }
|
|
7124
|
-
});
|
|
7125
|
-
},
|
|
7126
|
-
update: async (collection, id, data) => {
|
|
7127
|
-
bucket(collection).set(id, {
|
|
7128
|
-
id,
|
|
7129
|
-
data: { ...data }
|
|
7130
|
-
});
|
|
7131
|
-
},
|
|
7132
|
-
remove: async (collection, id) => {
|
|
7133
|
-
bucket(collection).delete(id);
|
|
7134
|
-
},
|
|
7135
|
-
declareCollection: async (collection) => {
|
|
7136
|
-
bucket(collection);
|
|
7137
|
-
}
|
|
7138
|
-
};
|
|
7139
|
-
}
|
|
7140
7088
|
/** The canned notification `testTarget` sends when the caller omits a sample. */
|
|
7141
7089
|
function cannedNotification() {
|
|
7142
7090
|
return {
|
|
@@ -7286,6 +7234,78 @@ function createHaNotificationOutputProvider(deps) {
|
|
|
7286
7234
|
};
|
|
7287
7235
|
}
|
|
7288
7236
|
//#endregion
|
|
7237
|
+
//#region src/ha-broker-store.ts
|
|
7238
|
+
/**
|
|
7239
|
+
* CRUD for HA brokers over the `brokers` entry record kind (B2).
|
|
7240
|
+
*/
|
|
7241
|
+
function rowToEntry(id, data) {
|
|
7242
|
+
const parsed = HaBrokerEntrySchema.safeParse({
|
|
7243
|
+
id,
|
|
7244
|
+
name: data.name,
|
|
7245
|
+
auth: data.auth,
|
|
7246
|
+
integrationId: data.integrationId
|
|
7247
|
+
});
|
|
7248
|
+
return parsed.success ? parsed.data : null;
|
|
7249
|
+
}
|
|
7250
|
+
var HaBrokerStore = class HaBrokerStore {
|
|
7251
|
+
kind;
|
|
7252
|
+
constructor(kind) {
|
|
7253
|
+
this.kind = kind;
|
|
7254
|
+
}
|
|
7255
|
+
static of(records) {
|
|
7256
|
+
return new HaBrokerStore(records.kind(haBrokersEntryKind));
|
|
7257
|
+
}
|
|
7258
|
+
async listResolved() {
|
|
7259
|
+
const page = await this.kind.query({ limit: 1e3 });
|
|
7260
|
+
const out = [];
|
|
7261
|
+
for (const record of page.records) {
|
|
7262
|
+
let data = record.data;
|
|
7263
|
+
try {
|
|
7264
|
+
const resolved = await this.kind.getResolved(record.id);
|
|
7265
|
+
if (resolved !== null) data = resolved.data;
|
|
7266
|
+
} catch (err) {
|
|
7267
|
+
if (!(err instanceof RecordSecretUnavailableError)) throw err;
|
|
7268
|
+
}
|
|
7269
|
+
const entry = rowToEntry(record.id, data);
|
|
7270
|
+
if (entry !== null) out.push(entry);
|
|
7271
|
+
}
|
|
7272
|
+
return out;
|
|
7273
|
+
}
|
|
7274
|
+
async getById(id) {
|
|
7275
|
+
try {
|
|
7276
|
+
const row = await this.kind.getResolved(id);
|
|
7277
|
+
if (row === null) return null;
|
|
7278
|
+
return rowToEntry(row.id, row.data);
|
|
7279
|
+
} catch (err) {
|
|
7280
|
+
if (err instanceof RecordSecretUnavailableError) return null;
|
|
7281
|
+
throw err;
|
|
7282
|
+
}
|
|
7283
|
+
}
|
|
7284
|
+
async upsert(entry) {
|
|
7285
|
+
const { id, name, auth, integrationId } = entry;
|
|
7286
|
+
const body = {
|
|
7287
|
+
name,
|
|
7288
|
+
auth,
|
|
7289
|
+
...integrationId === void 0 ? {} : { integrationId }
|
|
7290
|
+
};
|
|
7291
|
+
const parsed = haBrokersEntryKind.schema.safeParse(body);
|
|
7292
|
+
if (!parsed.success) throw new Error(`invalid HA broker '${id}': ${parsed.error.message}`);
|
|
7293
|
+
await this.kind.put({
|
|
7294
|
+
id,
|
|
7295
|
+
data: parsed.data
|
|
7296
|
+
});
|
|
7297
|
+
}
|
|
7298
|
+
async delete(id) {
|
|
7299
|
+
await this.kind.delete(id);
|
|
7300
|
+
}
|
|
7301
|
+
async replaceAll(entries) {
|
|
7302
|
+
const page = await this.kind.query({ limit: 1e3 });
|
|
7303
|
+
const nextIds = new Set(entries.map((e) => e.id));
|
|
7304
|
+
for (const row of page.records) if (!nextIds.has(row.id)) await this.kind.delete(row.id);
|
|
7305
|
+
for (const entry of entries) await this.upsert(entry);
|
|
7306
|
+
}
|
|
7307
|
+
};
|
|
7308
|
+
//#endregion
|
|
7289
7309
|
//#region src/ha-calendar-source.ts
|
|
7290
7310
|
function isCalendarEntity(entityId) {
|
|
7291
7311
|
return entityId.startsWith("calendar.");
|
|
@@ -7946,6 +7966,9 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
7946
7966
|
return `ha:${String(config?.["brokerId"] ?? "unknown")}:dev:${String(config?.["haDeviceId"] ?? Date.now())}`;
|
|
7947
7967
|
}
|
|
7948
7968
|
registry = null;
|
|
7969
|
+
brokerStore = null;
|
|
7970
|
+
/** Runtime broker rows (records when B2 is live, else settings). */
|
|
7971
|
+
activeBrokers = [];
|
|
7949
7972
|
/** Periodic re-sync sweep handle (cleared on shutdown). */
|
|
7950
7973
|
resyncTimer = null;
|
|
7951
7974
|
/** `brokerId → aggregator deviceId` cache for the per-broker generic
|
|
@@ -7959,6 +7982,11 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
7959
7982
|
super({ ...DEFAULT_CONFIG });
|
|
7960
7983
|
}
|
|
7961
7984
|
async onInitialize() {
|
|
7985
|
+
const records = this.ctx.records;
|
|
7986
|
+
if (records === void 0) throw new Error("provider-homeassistant: ctx.records is required (hub-only addon)");
|
|
7987
|
+
await records.declare(haAllRecordKinds);
|
|
7988
|
+
this.brokerStore = HaBrokerStore.of(records);
|
|
7989
|
+
this.activeBrokers = await this.loadPersistedBrokers();
|
|
7962
7990
|
this.registry = new HaBrokerRegistry(this.ctx.logger, this.ctx.eventBus);
|
|
7963
7991
|
this.registry.setOnBrokerConnected((brokerId) => {
|
|
7964
7992
|
this.reconcileBroker(brokerId);
|
|
@@ -7969,11 +7997,11 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
7969
7997
|
this.registry.setOnBusEvent((brokerId, ev) => {
|
|
7970
7998
|
this.routeBusEvent(brokerId, ev);
|
|
7971
7999
|
});
|
|
7972
|
-
await this.registry.restore(this.
|
|
8000
|
+
await this.registry.restore(this.activeBrokers);
|
|
7973
8001
|
this.resyncTimer = setInterval(() => {
|
|
7974
8002
|
this.reconcileAllConnectedBrokers();
|
|
7975
8003
|
}, HaProviderAddon.RESYNC_INTERVAL_MS);
|
|
7976
|
-
this.ctx.logger.info("ha provider initialised", { meta: { brokerCount: this.
|
|
8004
|
+
this.ctx.logger.info("ha provider initialised", { meta: { brokerCount: this.activeBrokers.length } });
|
|
7977
8005
|
await this.reconcileIntegrationsToBrokers();
|
|
7978
8006
|
this.subscribeIntegrationLifecycle();
|
|
7979
8007
|
return [
|
|
@@ -8004,11 +8032,9 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
8004
8032
|
];
|
|
8005
8033
|
}
|
|
8006
8034
|
async buildNotificationOutputProvider() {
|
|
8007
|
-
const
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
const store = new HaTargetStore(port);
|
|
8011
|
-
await store.init();
|
|
8035
|
+
const records = this.ctx.records;
|
|
8036
|
+
if (records === void 0) throw new Error("ha notification-output: ctx.records is required (hub-only addon)");
|
|
8037
|
+
const store = HaTargetStore.of(records);
|
|
8012
8038
|
return createHaNotificationOutputProvider({
|
|
8013
8039
|
addonId: this.ctx.id,
|
|
8014
8040
|
iconUrl: HA_NOTIFICATION_ICON_URL,
|
|
@@ -8025,7 +8051,7 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
8025
8051
|
if (!manager) throw new Error(`ha-broker '${brokerId}' not found`);
|
|
8026
8052
|
return manager.sendMessage({ type: "get_services" });
|
|
8027
8053
|
},
|
|
8028
|
-
listBrokerIds: () => this.
|
|
8054
|
+
listBrokerIds: () => this.activeBrokers.map((b) => b.id),
|
|
8029
8055
|
logger: this.ctx.logger.child("notification-output")
|
|
8030
8056
|
});
|
|
8031
8057
|
}
|
|
@@ -8086,7 +8112,7 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
8086
8112
|
const settings = await api.integrations.getSettings.query({ id: integration.id });
|
|
8087
8113
|
const brokerId = typeof settings["brokerId"] === "string" ? settings["brokerId"] : null;
|
|
8088
8114
|
if (!brokerId) continue;
|
|
8089
|
-
if (!this.
|
|
8115
|
+
if (!this.activeBrokers.find((b) => b.id === brokerId)) {
|
|
8090
8116
|
this.ctx.logger.warn("integration→broker: linked broker not found", { meta: {
|
|
8091
8117
|
integrationId: integration.id,
|
|
8092
8118
|
brokerId
|
|
@@ -8099,11 +8125,11 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
8099
8125
|
brokerId
|
|
8100
8126
|
});
|
|
8101
8127
|
}
|
|
8102
|
-
const linkedBrokers = linkBrokersToIntegrations(this.
|
|
8103
|
-
if (linkedBrokers.some((b, i) => b !== this.
|
|
8104
|
-
const orphans = computeBrokerCleanup(this.
|
|
8128
|
+
const linkedBrokers = linkBrokersToIntegrations(this.activeBrokers, integrationsWithBrokerId);
|
|
8129
|
+
if (linkedBrokers.some((b, i) => b !== this.activeBrokers[i])) await this.persistBrokerEntries(linkedBrokers);
|
|
8130
|
+
const orphans = computeBrokerCleanup(this.activeBrokers, survivingIntegrationIds);
|
|
8105
8131
|
this.ctx.logger.info("integration→broker reconcile", { meta: {
|
|
8106
|
-
brokerCount: this.
|
|
8132
|
+
brokerCount: this.activeBrokers.length,
|
|
8107
8133
|
integrationCount: haIntegrations.length,
|
|
8108
8134
|
orphans: orphans.length
|
|
8109
8135
|
} });
|
|
@@ -8200,7 +8226,8 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
8200
8226
|
async onConfigChanged() {
|
|
8201
8227
|
const reg = this.registry;
|
|
8202
8228
|
if (!reg) return;
|
|
8203
|
-
|
|
8229
|
+
this.activeBrokers = await this.loadPersistedBrokers();
|
|
8230
|
+
const persisted = this.activeBrokers;
|
|
8204
8231
|
const liveIds = new Set(reg.list().map((b) => b.id));
|
|
8205
8232
|
const persistedIds = new Set(persisted.map((e) => e.id));
|
|
8206
8233
|
for (const liveId of liveIds) if (!persistedIds.has(liveId)) try {
|
|
@@ -8238,7 +8265,7 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
8238
8265
|
...b,
|
|
8239
8266
|
id: created.id
|
|
8240
8267
|
} : b);
|
|
8241
|
-
await this.
|
|
8268
|
+
await this.persistBrokerEntries(next);
|
|
8242
8269
|
}
|
|
8243
8270
|
} catch (err) {
|
|
8244
8271
|
this.ctx.logger.warn("ha onConfigChanged: failed to start new broker", { meta: {
|
|
@@ -8248,9 +8275,26 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
8248
8275
|
}
|
|
8249
8276
|
}
|
|
8250
8277
|
}
|
|
8278
|
+
async loadPersistedBrokers() {
|
|
8279
|
+
const store = this.brokerStore;
|
|
8280
|
+
if (store !== null) {
|
|
8281
|
+
const fromRecords = await store.listResolved();
|
|
8282
|
+
if (fromRecords.length > 0) return fromRecords;
|
|
8283
|
+
}
|
|
8284
|
+
return this.config.brokers;
|
|
8285
|
+
}
|
|
8286
|
+
async persistBrokerEntries(entries) {
|
|
8287
|
+
this.activeBrokers = [...entries];
|
|
8288
|
+
const store = this.brokerStore;
|
|
8289
|
+
if (store !== null) {
|
|
8290
|
+
await store.replaceAll(entries);
|
|
8291
|
+
return;
|
|
8292
|
+
}
|
|
8293
|
+
await this.updateGlobalSettings({ brokers: entries });
|
|
8294
|
+
}
|
|
8251
8295
|
buildBrokerProvider() {
|
|
8252
8296
|
const ownerAddonId = this.ctx.id;
|
|
8253
|
-
const owns = (id) => this.
|
|
8297
|
+
const owns = (id) => this.activeBrokers.some((b) => b.id === id);
|
|
8254
8298
|
return {
|
|
8255
8299
|
list: async ({ kind }) => {
|
|
8256
8300
|
if (kind && kind !== HA_KIND) return [];
|
|
@@ -8280,7 +8324,7 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
8280
8324
|
brokerId: entry.id,
|
|
8281
8325
|
name
|
|
8282
8326
|
} });
|
|
8283
|
-
await this.
|
|
8327
|
+
await this.persistBrokerEntries([...this.activeBrokers, entry]);
|
|
8284
8328
|
return { id: entry.id };
|
|
8285
8329
|
},
|
|
8286
8330
|
remove: async ({ id }) => {
|
|
@@ -8295,8 +8339,8 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
8295
8339
|
brokerId: id,
|
|
8296
8340
|
logger: this.ctx.logger
|
|
8297
8341
|
});
|
|
8298
|
-
const nextBrokers = this.
|
|
8299
|
-
await this.
|
|
8342
|
+
const nextBrokers = this.activeBrokers.filter((b) => b.id !== id);
|
|
8343
|
+
await this.persistBrokerEntries(nextBrokers);
|
|
8300
8344
|
},
|
|
8301
8345
|
testConnection: async ({ id }) => {
|
|
8302
8346
|
if (!owns(id)) return {
|
|
@@ -8318,18 +8362,18 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
|
|
|
8318
8362
|
};
|
|
8319
8363
|
},
|
|
8320
8364
|
getSettings: async ({ id }) => {
|
|
8321
|
-
const entry = this.
|
|
8365
|
+
const entry = this.activeBrokers.find((b) => b.id === id);
|
|
8322
8366
|
if (!entry) return null;
|
|
8323
8367
|
return authToSettings(entry.auth);
|
|
8324
8368
|
},
|
|
8325
8369
|
setSettings: async ({ id, settings }) => {
|
|
8326
8370
|
if (!owns(id)) return;
|
|
8327
8371
|
const entry = await this.requireRegistry().updateEntry(id, settings);
|
|
8328
|
-
const next = this.
|
|
8329
|
-
await this.
|
|
8372
|
+
const next = this.activeBrokers.map((b) => b.id === id ? entry : b);
|
|
8373
|
+
await this.persistBrokerEntries(next);
|
|
8330
8374
|
},
|
|
8331
8375
|
getBrokerConfig: async ({ id }) => {
|
|
8332
|
-
const entry = this.
|
|
8376
|
+
const entry = this.activeBrokers.find((b) => b.id === id);
|
|
8333
8377
|
if (!entry) return null;
|
|
8334
8378
|
return authToSettings(entry.auth);
|
|
8335
8379
|
},
|