@camstack/addon-provider-homeassistant 1.2.141 → 1.2.142

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 CHANGED
@@ -1,7 +1,108 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dist = require("./dist-DdU8yrnj.js");
2
+ const require_dist = require("./dist-1gj-XZ-R.js");
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_zlib = require("node:zlib");
5
+ //#region src/ha-admin-link.ts
6
+ /**
7
+ * `device-admin-link` for Home-Assistant-backed devices.
8
+ *
9
+ * The interesting case of this capability, and the one that covers 779 of the
10
+ * 1 012 devices on this hub. An HA-backed device has **no web server of its
11
+ * own** — a Zigbee contact sensor has no IP at all — so its "configuration
12
+ * page" is a page inside Home Assistant, built from the broker's base URL plus
13
+ * the HA identifier the device already carries.
14
+ *
15
+ * Two subjects, because HA has two identifiers and they address different pages:
16
+ *
17
+ * - **A container** is an HA *device* (registry id, `ha:<broker>:dev:<id>`).
18
+ * Its page is `/config/devices/device/<haDeviceId>` — the actual device
19
+ * configuration page, with its entities, its area and its integration.
20
+ * - **A child or standalone device** is an HA *entity*. HA has no stable
21
+ * per-entity configuration URL (`/config/entities` is a list with a modal;
22
+ * there is no deep link into it), so the honest address for one entity is
23
+ * `/history?entity_id=<entityId>`. That is a real page about exactly that
24
+ * entity. Inventing a `/config/entities/<id>` route that HA does not serve
25
+ * would be a button that opens a 404, which is worse than no button.
26
+ *
27
+ * **What this cannot answer, and does not pretend to.** A broker authenticated
28
+ * by SUPERVISOR TOKEN carries no `baseUrl` at all — the supervisor proxy serves
29
+ * HA on a relative path and the instance's address is never exposed to the
30
+ * addon. Those devices get `null`, with a log line naming the reason. The cap
31
+ * is still registered for them: this provider CAN answer the question for HA
32
+ * devices in general, and for these particular ones the answer is "no page I
33
+ * can address", which is a different statement from a Petkit feeder's, whose
34
+ * provider never registers at all.
35
+ *
36
+ * **No credentials.** The broker config that holds `baseUrl` holds the
37
+ * long-lived `accessToken` next to it. The token is never read here and never
38
+ * travels; `parseAdminBase` REFUSES a base URL that carries userinfo rather
39
+ * than silently stripping it, so an operator who typed his HA login into the
40
+ * base-URL field loses the button instead of publishing the login.
41
+ */
42
+ function pageFor(subject, basePath) {
43
+ if (subject.kind === "device") {
44
+ if (subject.haDeviceId.length === 0) return null;
45
+ return { path: `${basePath}/config/devices/device/${encodeURIComponent(subject.haDeviceId)}` };
46
+ }
47
+ if (subject.entityId.length === 0) return null;
48
+ return {
49
+ path: `${basePath}/history`,
50
+ query: { entity_id: subject.entityId }
51
+ };
52
+ }
53
+ /**
54
+ * Register `device-admin-link` for one HA-backed device.
55
+ *
56
+ * The base URL is fetched from the broker on first ask and memoised, the same
57
+ * read-through `ha-image-device.ts` uses — a device never keeps a second copy
58
+ * of a broker fact (D224), and the memo is dropped on refusal so a broker that
59
+ * gains a `baseUrl` later starts answering without a restart.
60
+ */
61
+ function registerHaAdminLink(ctx, deviceId, brokerId, subject) {
62
+ let cachedBaseUrl = null;
63
+ const refuse = (reason, meta = {}) => {
64
+ ctx.logger.debug("home-assistant admin link unavailable", {
65
+ tags: { deviceId },
66
+ meta: {
67
+ reason,
68
+ brokerId,
69
+ subject: subject.kind,
70
+ ...meta
71
+ }
72
+ });
73
+ return null;
74
+ };
75
+ ctx.registerNativeCap(require_dist.deviceAdminLinkCapability, { getAdminLink: async (input) => {
76
+ if (input.deviceId !== deviceId) throw new Error(`HaAdminLink: deviceId mismatch, expected ${deviceId}, got ${input.deviceId}`);
77
+ if (cachedBaseUrl === null) {
78
+ const raw = (await ctx.api.broker.getBrokerConfig.query({ id: brokerId }))?.["baseUrl"];
79
+ if (typeof raw !== "string" || raw.length === 0) return refuse("no-base-url");
80
+ cachedBaseUrl = raw;
81
+ }
82
+ const base = require_dist.parseAdminBase(cachedBaseUrl);
83
+ if (!base.ok) {
84
+ cachedBaseUrl = null;
85
+ return refuse("base-unusable", { baseReason: base.reason });
86
+ }
87
+ const page = pageFor(subject, base.basePath);
88
+ if (page === null) return refuse("empty-subject");
89
+ const built = require_dist.buildDeviceAdminUrl({
90
+ host: base.host,
91
+ port: base.port,
92
+ scheme: base.scheme,
93
+ path: page.path,
94
+ ...page.query !== void 0 ? { query: page.query } : {}
95
+ });
96
+ if (!built.ok) return refuse("build-refused", { buildReason: built.reason });
97
+ return {
98
+ url: built.url,
99
+ label: "Home Assistant",
100
+ target: "integration",
101
+ host: base.host
102
+ };
103
+ } });
104
+ }
105
+ //#endregion
5
106
  //#region src/ha-device-source.ts
6
107
  /**
7
108
  * Shared zod fields merged into the placeholder + child device schemas.
@@ -308,6 +409,10 @@ var HaBaseChildDevice = class HaBaseChildDevice extends require_dist.BaseDevice
308
409
  */
309
410
  async onActivate() {
310
411
  await super.onActivate();
412
+ registerHaAdminLink(this.ctx, this.id, this.brokerId, {
413
+ kind: "entity",
414
+ entityId: this.entityId
415
+ });
311
416
  await this.refreshFeatures();
312
417
  if (await this.subscribeBrokerEntity()) {
313
418
  this.deviceLogger.info("HA entity ready", { meta: {
@@ -3695,6 +3800,10 @@ var HaContainerDevice = class HaContainerDevice extends require_dist.BaseDevice
3695
3800
  }
3696
3801
  async onActivate() {
3697
3802
  await super.onActivate();
3803
+ registerHaAdminLink(this.ctx, this.id, this.brokerId, {
3804
+ kind: "device",
3805
+ haDeviceId: this.config.values.haDeviceId
3806
+ });
3698
3807
  if (this.entityIds.size > 0) {
3699
3808
  this.attachAvailabilityListener();
3700
3809
  try {
package/dist/addon.mjs CHANGED
@@ -1,6 +1,107 @@
1
- import { $ as presenceCapability, A as controlCapability, At as EventCategory, B as humiditySensorCapability, C as buildAddonRouteProvider, Ct as number, D as colorCapability, Dt as union, E as climateControlCapability, Et as string, F as eventEmitterCapability, G as motionCapability, H as lawnMowerControlCapability, I as fanControlCapability, J as notifierCapability, K as normalizeUnit, L as floodCapability, M as deviceAdoptionCapability, O as connectivityCapability, Ot as unknown, P as enumSensorCapability, Q as prepareNotification, R as gasCapability, S as brokerCapability, St as literal, T as carbonMonoxideCapability, Tt as record, U as lockControlCapability, V as imageCapability, W as mediaPlayerCapability, Y as numericSensorCapability, Z as powerMeterCapability, _ as automationControlCapability, _t as createEvent, at as temperatureSensorCapability, b as binaryCapability, bt as array, ct as valveCapability, d as TargetSchema, dt as weatherCapability, et as pressureSensorCapability, f as accessoriesCapability, ft as errMsg, g as ambientLightSensorCapability, gt as DeviceType, h as alarmPanelCapability, ht as DeviceRole, it as tamperCapability, j as coverCapability, k as contactCapability, kt as url, lt as vibrationCapability, m as airQualitySensorCapability, mt as DeviceFeature, n as BaseDevice, nt as smokeCapability, ot as updateCapability, p as addonRoutesCapability, q as notificationOutputCapability, r as BaseDeviceProvider, rt as switchCapability, s as EnumSensorDateTimeFormatSchema, st as vacuumControlCapability, tt as scriptRunnerCapability, ut as waterHeaterCapability, v as batteryCapability, w as buttonCapability, wt as object, x as brightnessCapability, xt as discriminatedUnion, y as bestLocationMatch, yt as _enum, z as humidifierCapability } from "./dist-R2qhuV_9.mjs";
1
+ import { $ as parseAdminBase, A as contactCapability, At as union, B as gasCapability, C as buildAddonRouteProvider, Ct as array, D as climateControlCapability, Dt as object, E as carbonMonoxideCapability, Et as number, G as lockControlCapability, H as humiditySensorCapability, I as enumSensorCapability, J as normalizeUnit, K as mediaPlayerCapability, L as eventEmitterCapability, M as coverCapability, Mt as url, N as deviceAdminLinkCapability, Nt as EventCategory, O as colorCapability, Ot as record, P as deviceAdoptionCapability, R as fanControlCapability, S as brokerCapability, St as _enum, T as buttonCapability, Tt as literal, U as imageCapability, V as humidifierCapability, W as lawnMowerControlCapability, X as notifierCapability, Y as notificationOutputCapability, Z as numericSensorCapability, _ as automationControlCapability, _t as DeviceFeature, at as smokeCapability, b as binaryCapability, bt as createEvent, ct as temperatureSensorCapability, d as TargetSchema, dt as valveCapability, et as powerMeterCapability, f as accessoriesCapability, ft as vibrationCapability, g as ambientLightSensorCapability, h as alarmPanelCapability, ht as errMsg, it as scriptRunnerCapability, j as controlCapability, jt as unknown, k as connectivityCapability, kt as string, lt as updateCapability, m as airQualitySensorCapability, mt as weatherCapability, n as BaseDevice, nt as presenceCapability, ot as switchCapability, p as addonRoutesCapability, pt as waterHeaterCapability, q as motionCapability, r as BaseDeviceProvider, rt as pressureSensorCapability, s as EnumSensorDateTimeFormatSchema, st as tamperCapability, tt as prepareNotification, ut as vacuumControlCapability, v as batteryCapability, vt as DeviceRole, w as buildDeviceAdminUrl, wt as discriminatedUnion, x as brightnessCapability, y as bestLocationMatch, yt as DeviceType, z as floodCapability } from "./dist-DbcraPOl.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
4
+ //#region src/ha-admin-link.ts
5
+ /**
6
+ * `device-admin-link` for Home-Assistant-backed devices.
7
+ *
8
+ * The interesting case of this capability, and the one that covers 779 of the
9
+ * 1 012 devices on this hub. An HA-backed device has **no web server of its
10
+ * own** — a Zigbee contact sensor has no IP at all — so its "configuration
11
+ * page" is a page inside Home Assistant, built from the broker's base URL plus
12
+ * the HA identifier the device already carries.
13
+ *
14
+ * Two subjects, because HA has two identifiers and they address different pages:
15
+ *
16
+ * - **A container** is an HA *device* (registry id, `ha:<broker>:dev:<id>`).
17
+ * Its page is `/config/devices/device/<haDeviceId>` — the actual device
18
+ * configuration page, with its entities, its area and its integration.
19
+ * - **A child or standalone device** is an HA *entity*. HA has no stable
20
+ * per-entity configuration URL (`/config/entities` is a list with a modal;
21
+ * there is no deep link into it), so the honest address for one entity is
22
+ * `/history?entity_id=<entityId>`. That is a real page about exactly that
23
+ * entity. Inventing a `/config/entities/<id>` route that HA does not serve
24
+ * would be a button that opens a 404, which is worse than no button.
25
+ *
26
+ * **What this cannot answer, and does not pretend to.** A broker authenticated
27
+ * by SUPERVISOR TOKEN carries no `baseUrl` at all — the supervisor proxy serves
28
+ * HA on a relative path and the instance's address is never exposed to the
29
+ * addon. Those devices get `null`, with a log line naming the reason. The cap
30
+ * is still registered for them: this provider CAN answer the question for HA
31
+ * devices in general, and for these particular ones the answer is "no page I
32
+ * can address", which is a different statement from a Petkit feeder's, whose
33
+ * provider never registers at all.
34
+ *
35
+ * **No credentials.** The broker config that holds `baseUrl` holds the
36
+ * long-lived `accessToken` next to it. The token is never read here and never
37
+ * travels; `parseAdminBase` REFUSES a base URL that carries userinfo rather
38
+ * than silently stripping it, so an operator who typed his HA login into the
39
+ * base-URL field loses the button instead of publishing the login.
40
+ */
41
+ function pageFor(subject, basePath) {
42
+ if (subject.kind === "device") {
43
+ if (subject.haDeviceId.length === 0) return null;
44
+ return { path: `${basePath}/config/devices/device/${encodeURIComponent(subject.haDeviceId)}` };
45
+ }
46
+ if (subject.entityId.length === 0) return null;
47
+ return {
48
+ path: `${basePath}/history`,
49
+ query: { entity_id: subject.entityId }
50
+ };
51
+ }
52
+ /**
53
+ * Register `device-admin-link` for one HA-backed device.
54
+ *
55
+ * The base URL is fetched from the broker on first ask and memoised, the same
56
+ * read-through `ha-image-device.ts` uses — a device never keeps a second copy
57
+ * of a broker fact (D224), and the memo is dropped on refusal so a broker that
58
+ * gains a `baseUrl` later starts answering without a restart.
59
+ */
60
+ function registerHaAdminLink(ctx, deviceId, brokerId, subject) {
61
+ let cachedBaseUrl = null;
62
+ const refuse = (reason, meta = {}) => {
63
+ ctx.logger.debug("home-assistant admin link unavailable", {
64
+ tags: { deviceId },
65
+ meta: {
66
+ reason,
67
+ brokerId,
68
+ subject: subject.kind,
69
+ ...meta
70
+ }
71
+ });
72
+ return null;
73
+ };
74
+ ctx.registerNativeCap(deviceAdminLinkCapability, { getAdminLink: async (input) => {
75
+ if (input.deviceId !== deviceId) throw new Error(`HaAdminLink: deviceId mismatch, expected ${deviceId}, got ${input.deviceId}`);
76
+ if (cachedBaseUrl === null) {
77
+ const raw = (await ctx.api.broker.getBrokerConfig.query({ id: brokerId }))?.["baseUrl"];
78
+ if (typeof raw !== "string" || raw.length === 0) return refuse("no-base-url");
79
+ cachedBaseUrl = raw;
80
+ }
81
+ const base = parseAdminBase(cachedBaseUrl);
82
+ if (!base.ok) {
83
+ cachedBaseUrl = null;
84
+ return refuse("base-unusable", { baseReason: base.reason });
85
+ }
86
+ const page = pageFor(subject, base.basePath);
87
+ if (page === null) return refuse("empty-subject");
88
+ const built = buildDeviceAdminUrl({
89
+ host: base.host,
90
+ port: base.port,
91
+ scheme: base.scheme,
92
+ path: page.path,
93
+ ...page.query !== void 0 ? { query: page.query } : {}
94
+ });
95
+ if (!built.ok) return refuse("build-refused", { buildReason: built.reason });
96
+ return {
97
+ url: built.url,
98
+ label: "Home Assistant",
99
+ target: "integration",
100
+ host: base.host
101
+ };
102
+ } });
103
+ }
104
+ //#endregion
4
105
  //#region src/ha-device-source.ts
5
106
  /**
6
107
  * Shared zod fields merged into the placeholder + child device schemas.
@@ -307,6 +408,10 @@ var HaBaseChildDevice = class HaBaseChildDevice extends BaseDevice {
307
408
  */
308
409
  async onActivate() {
309
410
  await super.onActivate();
411
+ registerHaAdminLink(this.ctx, this.id, this.brokerId, {
412
+ kind: "entity",
413
+ entityId: this.entityId
414
+ });
310
415
  await this.refreshFeatures();
311
416
  if (await this.subscribeBrokerEntity()) {
312
417
  this.deviceLogger.info("HA entity ready", { meta: {
@@ -3694,6 +3799,10 @@ var HaContainerDevice = class HaContainerDevice extends BaseDevice {
3694
3799
  }
3695
3800
  async onActivate() {
3696
3801
  await super.onActivate();
3802
+ registerHaAdminLink(this.ctx, this.id, this.brokerId, {
3803
+ kind: "device",
3804
+ haDeviceId: this.config.values.haDeviceId
3805
+ });
3697
3806
  if (this.entityIds.size > 0) {
3698
3807
  this.attachAvailabilityListener();
3699
3808
  try {