@camstack/addon-provider-homeassistant 1.2.638 → 1.2.640

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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as lawnMowerControlCapability, $t as object, A as buttonCapability, At as valveCapability, B as coverCapability, Bt as baseUnitOf, C as batteryCapability, Ct as switchCapability, D as brokerCapability, Dt as tryConvertUnit, E as brightnessCapability, Et as tryConvertDelta, F as connectivityCapability, Ft as DeviceRole, G as eventEmitterCapability, H as deviceAdoptionCapability, Ht as defineEntryKind, It as DeviceType, J as gasCapability, Jt as array, K as fanControlCapability, Lt as errMsg, M as carbonMonoxideCapability, Mt as waterHeaterCapability, N as climateControlCapability, Nt as weatherCapability, O as buildAddonRouteProvider, Ot as updateCapability, P as colorCapability, Pt as DeviceFeature, Q as ingestReading, Qt as number, R as contactCapability, S as automationControlCapability, St as smokeCapability, T as binaryCapability, Tt as temperatureSensorCapability, Ut as defineRecordKinds, V as deviceAdminLinkCapability, Vt as createEvent, W as enumSensorCapability, Wt as entryBody, X as humiditySensorCapability, Xt as discriminatedUnion, Y as humidifierCapability, Yt as boolean, Z as imageCapability, Zt as literal, _ as accessoriesCapability, _t as runningFromMediaPlayer, an as EventCategory, at as notifierCapability, b as alarmPanelCapability, bt as scriptRunnerCapability, ct as parseAdminBase, dt as presenceCapability, en as record, et as lockControlCapability, ft as pressureSensorCapability, g as TargetSchema, gt as runningFromLawnMower, h as RUNNING_STATE_COLD, in as url, it as notificationOutputCapability, j as calendarSourceCapability, jt as vibrationCapability, k as buildDeviceAdminUrl, kt as vacuumControlCapability, lt as powerMeterCapability, mt as quantityFromUnit, n as BaseDevice, nn as union, nt as motionCapability, ot as numericSensorCapability, q as floodCapability, qt as _enum, r as BaseDeviceProvider, rn as unknown, rt as normalizeUnit, tn as string, tt as mediaPlayerCapability, u as EnumSensorDateTimeFormatSchema, ut as prepareNotification, v as addonRoutesCapability, vt as runningFromVacuumState, w as bestLocationMatch, wt as tamperCapability, x as ambientLightSensorCapability, y as airQualitySensorCapability, yt as runningStateCapability, z as controlCapability, zt as RecordSecretUnavailableError } from "./dist-Cvz76AD1.mjs";
1
+ import { $ as humiditySensorCapability, $t as discriminatedUnion, A as brokerCapability, At as tryConvertUnit, Bt as errMsg, C as alarmPanelCapability, Ct as scriptRunnerCapability, D as bestLocationMatch, Dt as tamperCapability, E as batteryCapability, Et as switchCapability, F as carbonMonoxideCapability, Ft as waterHeaterCapability, G as deviceAdoptionCapability, Gt as defineEntryKind, H as controlCapability, Ht as RecordSecretUnavailableError, I as climateControlCapability, It as weatherCapability, J as eventEmitterCapability, Kt as defineRecordKinds, L as colorCapability, Lt as DeviceFeature, M as buildDeviceAdminUrl, Mt as vacuumControlCapability, N as buttonCapability, Nt as valveCapability, O as binaryCapability, Ot as temperatureSensorCapability, P as calendarSourceCapability, Pt as vibrationCapability, Q as humidifierCapability, Qt as boolean, R as connectivityCapability, Rt as DeviceRole, S as airQualitySensorCapability, St as runningStateCapability, T as automationControlCapability, Tt as smokeCapability, U as coverCapability, Ut as baseUnitOf, V as contactCapability, W as deviceAdminLinkCapability, Wt as createEvent, X as floodCapability, Xt as _enum, Y as fanControlCapability, Z as gasCapability, Zt as array, _t as quantityFromUnit, a as BaseDevice, an as union, at as motionCapability, b as accessoriesCapability, bt as runningFromMediaPlayer, cn as EventCategory, ct as notifierCapability, dt as parseAdminBase, en as literal, et as imageCapability, ft as powerMeterCapability, ht as pressureSensorCapability, in as string, it as mediaPlayerCapability, j as buildAddonRouteProvider, jt as updateCapability, k as brightnessCapability, kt as tryConvertDelta, lt as numericSensorCapability, mt as presenceCapability, nn as object, nt as lawnMowerControlCapability, o as BaseDeviceProvider, on as unknown, ot as normalizeUnit, p as EnumSensorDateTimeFormatSchema, pt as prepareNotification, q as enumSensorCapability, qt as entryBody, rn as record, rt as lockControlCapability, sn as url, st as notificationOutputCapability, t as HA_BROKER_KIND, tn as number, tt as ingestReading, v as RUNNING_STATE_COLD, w as ambientLightSensorCapability, x as addonRoutesCapability, xt as runningFromVacuumState, y as TargetSchema, yt as runningFromLawnMower, zt as DeviceType } from "./ha-broker-source-FHFpUlgq.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
@@ -5353,8 +5353,10 @@ function subscribeEntities(opts) {
5353
5353
  * the same HA broker for overlapping entity sets. We keep ONE
5354
5354
  * upstream `subscribe_entities` to HA with the UNION of every
5355
5355
  * active subscription's entity ids, and fan inbound state changes
5356
- * out via `EventCategory.BrokerMessage` once per matching
5357
- * subscription.
5356
+ * out via `EventCategory.BrokerMessage` ONCE per change when at
5357
+ * least one subscription matches (the bus is shared and consumers
5358
+ * filter by `key`). A new subscriber's cache replay is the one
5359
+ * targeted emission: it carries that subscription's id.
5358
5360
  * - When the active union changes (subscribe / unsubscribe call),
5359
5361
  * we re-issue the upstream subscribe with the new set. HA accepts
5360
5362
  * this efficiently (server-side filter).
@@ -5366,6 +5368,10 @@ function subscribeEntities(opts) {
5366
5368
  * subscribe's initial `a` (add) frames.
5367
5369
  */
5368
5370
  var HA_KIND$1 = "home-assistant";
5371
+ /** `subscriptionId` of a `broker.message` that is not addressed to one
5372
+ * subscription (a live state change fanned out once). The payload type
5373
+ * requires the field; the empty string is the repo convention for "none". */
5374
+ var FANOUT_SUBSCRIPTION_ID = "";
5369
5375
  /**
5370
5376
  * Curated device-event allowlist for the per-broker bus-event aggregator
5371
5377
  * (Case 2). Home Assistant fires arbitrary events on its bus; subscribing
@@ -5644,7 +5650,10 @@ var HaIntegrationManager = class HaIntegrationManager {
5644
5650
  for (const entityId of changed) {
5645
5651
  const state = next[entityId] ?? null;
5646
5652
  if (state === null) continue;
5647
- for (const sub of this.subscriptions.values()) if (this.subscriptionMatches(sub, entityId)) this.emitBrokerMessage(sub.id, entityId, state);
5653
+ for (const sub of this.subscriptions.values()) if (this.subscriptionMatches(sub, entityId)) {
5654
+ this.emitBrokerMessage(FANOUT_SUBSCRIPTION_ID, entityId, state);
5655
+ break;
5656
+ }
5648
5657
  }
5649
5658
  }
5650
5659
  subscriptionMatches(sub, entityId) {
@@ -7221,21 +7230,27 @@ function haConfigSchema() {
7221
7230
  title: "Home Assistant notify",
7222
7231
  titleKey: "provider-homeassistant:haConfig.s_connection.title",
7223
7232
  fields: [{
7224
- type: "text",
7233
+ type: "broker-select",
7234
+ key: "brokerId",
7235
+ label: "Broker",
7236
+ labelKey: "provider-homeassistant:haConfig.s_connection.brokerId.label",
7237
+ description: "The Home Assistant connection the notification goes through.",
7238
+ descriptionKey: "provider-homeassistant:haConfig.s_connection.brokerId.description",
7239
+ brokerKind: HA_BROKER_KIND
7240
+ }, {
7241
+ type: "select",
7225
7242
  key: "service",
7226
7243
  label: "Notify service",
7227
7244
  labelKey: "provider-homeassistant:haConfig.s_connection.service.label",
7228
7245
  required: true,
7246
+ options: [],
7229
7247
  placeholder: "mobile_app_pixel_7",
7230
- description: "The part after `notify.` (e.g. `mobile_app_pixel_7`); use Discover to list them.",
7231
- descriptionKey: "provider-homeassistant:haConfig.s_connection.service.description"
7232
- }, {
7233
- type: "text",
7234
- key: "brokerId",
7235
- label: "Broker",
7236
- labelKey: "provider-homeassistant:haConfig.s_connection.brokerId.label",
7237
- description: "Optional; blank uses the only configured broker.",
7238
- descriptionKey: "provider-homeassistant:haConfig.s_connection.brokerId.description"
7248
+ description: "Loaded from the broker once it is picked. If the list does not load, type the part after `notify.`.",
7249
+ descriptionKey: "provider-homeassistant:haConfig.s_connection.service.description",
7250
+ discoverOptions: {
7251
+ kind: HA_NOTIFICATION_KIND,
7252
+ dependsOn: ["brokerId"]
7253
+ }
7239
7254
  }]
7240
7255
  }] };
7241
7256
  }
@@ -7376,24 +7391,70 @@ function buildNotifyServiceData(prepared) {
7376
7391
  * `get_services` returns `{ [domain]: { [service]: {...} } }`; we read the
7377
7392
  * `notify` domain. mobile_app_* services (rich push features) sort first.
7378
7393
  */
7379
- function parseNotifyServices(result, brokerId) {
7394
+ function parseNotifyServices(result, brokerId, deviceNames) {
7380
7395
  if (typeof result !== "object" || result === null) return [];
7381
7396
  const notify = result["notify"];
7382
7397
  if (typeof notify !== "object" || notify === null) return [];
7383
- return [...Object.keys(notify)].sort((a, b) => {
7398
+ return [...Object.entries(notify)].sort(([a], [b]) => {
7384
7399
  const am = a.startsWith("mobile_app_") ? 0 : 1;
7385
7400
  const bm = b.startsWith("mobile_app_") ? 0 : 1;
7386
7401
  if (am !== bm) return am - bm;
7387
7402
  return a.localeCompare(b);
7388
- }).map((service) => ({
7403
+ }).map(([service, definition]) => ({
7389
7404
  kind: HA_NOTIFICATION_KIND,
7390
- suggestedName: `notify.${service}`,
7405
+ suggestedName: friendlyServiceName(service, definition, deviceNames),
7391
7406
  config: {
7392
7407
  service,
7393
7408
  ...brokerId !== void 0 ? { brokerId } : {}
7394
7409
  }
7395
7410
  }));
7396
7411
  }
7412
+ function nonEmptyText(value) {
7413
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
7414
+ }
7415
+ /** `alexa_media_echo` -> `Alexa Media Echo`. */
7416
+ function prettifyServiceId(service) {
7417
+ return service.split("_").filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
7418
+ }
7419
+ /**
7420
+ * The name an operator recognises: the device name for a mobile app, else the
7421
+ * service's own `name`, else its `description`, else the prettified id.
7422
+ */
7423
+ function friendlyServiceName(service, definition, deviceNames) {
7424
+ const device = deviceNames?.get(service);
7425
+ if (device !== void 0) return device;
7426
+ if (typeof definition === "object" && definition !== null) {
7427
+ const fields = { ...definition };
7428
+ const name = nonEmptyText(fields["name"]);
7429
+ if (name !== void 0) return name;
7430
+ const description = nonEmptyText(fields["description"]);
7431
+ if (description !== void 0) return description;
7432
+ }
7433
+ return prettifyServiceId(service);
7434
+ }
7435
+ /** HA's slug of a device name, as it appears in `mobile_app_<slug>`. */
7436
+ function slugifyDeviceName(name) {
7437
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
7438
+ }
7439
+ /**
7440
+ * `mobile_app_<slug>` -> device name, read from a `config/device_registry/list`
7441
+ * reply. Only devices of the mobile_app integration count; the name the user
7442
+ * gave wins over the one the app reported.
7443
+ */
7444
+ function mobileAppDeviceNames(registry) {
7445
+ const names = /* @__PURE__ */ new Map();
7446
+ if (!Array.isArray(registry)) return names;
7447
+ for (const entry of registry) {
7448
+ if (typeof entry !== "object" || entry === null) continue;
7449
+ const device = { ...entry };
7450
+ const identifiers = device["identifiers"];
7451
+ if (!(Array.isArray(identifiers) && identifiers.some((pair) => Array.isArray(pair) && pair[0] === "mobile_app"))) continue;
7452
+ const name = nonEmptyText(device["name_by_user"]) ?? nonEmptyText(device["name"]);
7453
+ if (name === void 0) continue;
7454
+ names.set(`mobile_app_${slugifyDeviceName(name)}`, name);
7455
+ }
7456
+ return names;
7457
+ }
7397
7458
  /**
7398
7459
  * CRUD over the `ha-notification-targets` record kind.
7399
7460
  *
@@ -7547,12 +7608,24 @@ function createHaNotificationOutputProvider(deps) {
7547
7608
  },
7548
7609
  discoverTargets: async ({ kind, config }) => {
7549
7610
  if (kind !== "homeassistant") return [];
7611
+ const deviceNamesOf = async (brokerId) => {
7612
+ if (deps.listDevices === void 0) return /* @__PURE__ */ new Map();
7613
+ try {
7614
+ return mobileAppDeviceNames(await deps.listDevices(brokerId));
7615
+ } catch (err) {
7616
+ logger?.warn("ha device registry read failed; naming services from their ids", { meta: {
7617
+ brokerId,
7618
+ error: errMsg(err)
7619
+ } });
7620
+ return /* @__PURE__ */ new Map();
7621
+ }
7622
+ };
7550
7623
  const explicit = config ? readBrokerId(config) : void 0;
7551
7624
  const brokerIds = explicit ? [explicit] : [...deps.listBrokerIds()];
7552
7625
  const out = [];
7553
7626
  for (const brokerId of brokerIds) try {
7554
7627
  const result = await deps.getServices(brokerId);
7555
- out.push(...parseNotifyServices(result, brokerId));
7628
+ out.push(...parseNotifyServices(result, brokerId, await deviceNamesOf(brokerId)));
7556
7629
  } catch (err) {
7557
7630
  logger?.warn("ha get_services discovery failed", { meta: {
7558
7631
  brokerId,
@@ -8421,6 +8494,11 @@ var HaProviderAddon = class HaProviderAddon extends BaseDeviceProvider {
8421
8494
  if (!manager) throw new Error(`ha-broker '${brokerId}' not found`);
8422
8495
  return manager.sendMessage({ type: "get_services" });
8423
8496
  },
8497
+ listDevices: async (brokerId) => {
8498
+ const manager = this.requireRegistry().getManager(brokerId);
8499
+ if (!manager) throw new Error(`ha-broker '${brokerId}' not found`);
8500
+ return manager.sendMessage({ type: "config/device_registry/list" });
8501
+ },
8424
8502
  listBrokerIds: () => this.activeBrokers.map((b) => b.id),
8425
8503
  logger: this.ctx.logger.child("notification-output")
8426
8504
  });