@camstack/addon-provider-homeassistant 1.2.141 → 1.2.143
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 +110 -1
- package/dist/addon.mjs +110 -1
- package/dist/{dist-DdU8yrnj.js → dist-BQE3XuD4.js} +4089 -450
- package/dist/{dist-R2qhuV_9.mjs → dist-CcnfBSQ5.mjs} +4072 -451
- package/dist/ha-export/ha-export.addon.js +13 -5
- package/dist/ha-export/ha-export.addon.mjs +13 -5
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -1,7 +1,108 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dist = require("./dist-
|
|
2
|
+
const require_dist = require("./dist-BQE3XuD4.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
|
|
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-CcnfBSQ5.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 {
|