@hakit/core
Version:
A collection of React hooks and helpers for Home Assistant to easily communicate with the Home Assistant WebSocket API.
444 lines (443 loc) • 13.7 kB
JavaScript
import * as x from "react/jsx-runtime";
import * as T from "react";
import { forwardRef as Ce, useContext as Se, useRef as $, useCallback as g, useEffect as A } from "react";
import we from "@emotion/cache";
import { getRegisteredStyles as Pe, registerStyles as Oe, insertStyles as _e } from "@emotion/utils";
import { serializeStyles as Ne } from "@emotion/serialize";
import Ae from "react-is";
import { getStates as be, getServices as Ie, getConfig as He, getUser as X, subscribeEntities as Le, subscribeConfig as xe, callService as Ue, createLongLivedTokenAuth as je, createConnection as Z, getAuth as $e, ERR_CANNOT_CONNECT as k, ERR_INVALID_AUTH as oe, ERR_INVALID_HTTPS_TO_HTTP as De, ERR_HASS_HOST_REQUIRED as Fe, ERR_CONNECTION_LOST as Me } from "home-assistant-js-websocket";
import { isArray as ke, snakeCase as ee } from "lodash";
import { clearTokens as se, loadTokens as q, saveTokens as te } from "./es/HassConnect/token-storage.js";
import { useDebouncedCallback as We } from "use-debounce";
import { useStore as c, HassContext as qe } from "./es/HassConnect/HassContext.js";
var M, re;
function ze() {
if (re) return M;
re = 1;
var r = Ae, t = {
childContextTypes: !0,
contextType: !0,
contextTypes: !0,
defaultProps: !0,
displayName: !0,
getDefaultProps: !0,
getDerivedStateFromError: !0,
getDerivedStateFromProps: !0,
mixins: !0,
propTypes: !0,
type: !0
}, o = {
name: !0,
length: !0,
prototype: !0,
caller: !0,
callee: !0,
arguments: !0,
arity: !0
}, n = {
$$typeof: !0,
render: !0,
defaultProps: !0,
displayName: !0,
propTypes: !0
}, a = {
$$typeof: !0,
compare: !0,
defaultProps: !0,
displayName: !0,
propTypes: !0,
type: !0
}, l = {};
l[r.ForwardRef] = n, l[r.Memo] = a;
function s(p) {
return r.isMemo(p) ? a : l[p.$$typeof] || t;
}
var h = Object.defineProperty, y = Object.getOwnPropertyNames, v = Object.getOwnPropertySymbols, m = Object.getOwnPropertyDescriptor, C = Object.getPrototypeOf, i = Object.prototype;
function O(p, E, S) {
if (typeof E != "string") {
if (i) {
var b = C(E);
b && b !== i && O(p, b, S);
}
var d = y(E);
v && (d = d.concat(v(E)));
for (var U = s(p), _ = s(E), w = 0; w < d.length; ++w) {
var R = d[w];
if (!o[R] && !(S && S[R]) && !(_ && _[R]) && !(U && U[R])) {
var I = m(E, R);
try {
h(p, R, I);
} catch {
}
}
}
}
return p;
}
return M = O, M;
}
ze();
var Ve = function(t) {
return t();
}, Qe = T.useInsertionEffect ? T.useInsertionEffect : !1, Ye = Qe || Ve, ae = /* @__PURE__ */ T.createContext(
// we're doing this to avoid preconstruct's dead code elimination in this one case
// because this module is primarily intended for the browser and node
// but it's also required in react native and similar environments sometimes
// and we could have a special build just for that
// but this is much easier and the native packages
// might use a different theme context in the future anyway
typeof HTMLElement < "u" ? /* @__PURE__ */ we({
key: "css"
}) : null
);
ae.Provider;
var Be = function(t) {
return /* @__PURE__ */ Ce(function(o, n) {
var a = Se(ae);
return t(o, a, n);
});
}, Ge = /* @__PURE__ */ T.createContext({}), D = {}.hasOwnProperty, W = "__EMOTION_TYPE_PLEASE_DO_NOT_USE__", ce = function(t, o) {
var n = {};
for (var a in o)
D.call(o, a) && (n[a] = o[a]);
return n[W] = t, n;
}, Je = function(t) {
var o = t.cache, n = t.serialized, a = t.isStringTag;
return Oe(o, n, a), Ye(function() {
return _e(o, n, a);
}), null;
}, Ke = /* @__PURE__ */ Be(function(r, t, o) {
var n = r.css;
typeof n == "string" && t.registered[n] !== void 0 && (n = t.registered[n]);
var a = r[W], l = [n], s = "";
typeof r.className == "string" ? s = Pe(t.registered, l, r.className) : r.className != null && (s = r.className + " ");
var h = Ne(l, void 0, T.useContext(Ge));
s += t.key + "-" + h.name;
var y = {};
for (var v in r)
D.call(r, v) && v !== "css" && v !== W && (y[v] = r[v]);
return y.className = s, o && (y.ref = o), /* @__PURE__ */ T.createElement(T.Fragment, null, /* @__PURE__ */ T.createElement(Je, {
cache: t,
serialized: h,
isStringTag: typeof a == "string"
}), /* @__PURE__ */ T.createElement(a, y));
}), ie = Ke, lt = x.Fragment, ne = function(t, o, n) {
return D.call(o, "css") ? x.jsx(ie, ce(t, o), n) : x.jsx(t, o, n);
}, Xe = function(t, o, n) {
return D.call(o, "css") ? x.jsxs(ie, ce(t, o), n) : x.jsxs(t, o, n);
};
function L(r, t) {
const n = (() => {
switch (r) {
case oe:
return `ERR_INVALID_AUTH: Invalid authentication. ${t ? 'Check your "Long-Lived Access Token".' : ""}`;
case k:
return "ERR_CANNOT_CONNECT: Unable to connect";
case Me:
return "ERR_CONNECTION_LOST: Lost connection to home assistant.";
case Fe:
return "ERR_HASS_HOST_REQUIRED: Please enter a Home Assistant URL.";
case De:
return 'ERR_INVALID_HTTPS_TO_HTTP: Cannot connect to Home Assistant instances over "http://".';
default:
return null;
}
})();
return n !== null ? n : r?.error || r?.message || `Unknown Error (${r})`;
}
function ue() {
try {
return window.top?.hassConnection;
} catch (r) {
console.error("Error getting inherited connection", r);
return;
}
}
function Ze(r, t) {
const o = location && location.search.includes("auth_callback=1"), n = !!ue(), a = !!t, l = !!q(r, !1);
switch (!0) {
case o:
return "auth-callback";
case n:
return "inherited-auth";
case a:
return "provided-token";
case l:
return "saved-tokens";
default:
return "user-request";
}
}
const le = async (r, t) => {
const o = Ze(r, t);
if (o === "inherited-auth")
try {
const { auth: s, conn: h } = await ue();
return {
type: "success",
connection: h,
auth: s
};
} catch (s) {
return {
type: "error",
error: L(s, t)
};
}
if (o === "provided-token" && t)
try {
const s = await je(r, t);
return {
type: "success",
connection: await Z({ auth: s }),
auth: s
};
} catch (s) {
return {
type: "error",
error: L(s, t)
};
}
const n = {
saveTokens: te,
loadTokens: () => Promise.resolve(q(r))
};
if (r && o === "user-request") {
if (n.hassUrl = r, n.hassUrl === "")
return {
type: "error",
error: "Please enter a Home Assistant URL."
};
if (n.hassUrl.indexOf("://") === -1)
return {
type: "error",
error: "Please enter your full URL, including the protocol part (https://)."
};
try {
new URL(n.hassUrl);
} catch (s) {
return console.error("Error:", s), {
type: "error",
error: "Invalid URL"
};
}
}
let a;
try {
a = await $e(n);
} catch (s) {
return s?.error === "invalid_grant" ? (se(), le(r, t)) : o === "saved-tokens" && s === k ? {
type: "failed",
cannotConnect: !0
} : {
type: "error",
error: L(s, t)
};
} finally {
location && location.search.includes("auth_callback=1") && history.replaceState(null, "", location.pathname);
}
let l;
try {
l = await Z({ auth: a });
} catch (s) {
if (o === "saved-tokens") {
if (s === k)
return {
type: "failed",
cannotConnect: !0
};
s === oe && te(null);
}
return {
type: "error",
error: L(s, t)
};
}
return {
type: "success",
connection: l,
auth: a
};
};
function ft({ children: r, hassUrl: t, hassToken: o, portalRoot: n, windowContext: a }) {
const l = $(null), s = $(!1), h = $(null), y = c((e) => e.setHash), v = c((e) => e.hash), m = c((e) => e.routes), C = c((e) => e.setRoutes), i = c((e) => e.connection), O = c((e) => e.setConnection), p = $(null), E = c((e) => e.entities), S = c((e) => e.setEntities), b = c((e) => e.error), d = c((e) => e.setError), U = c((e) => e.cannotConnect), _ = c((e) => e.setCannotConnect), w = c((e) => e.setAuth), R = c((e) => e.triggerOnDisconnect), I = c((e) => e.ready), H = c((e) => e.setUser), z = c((e) => e.setReady), j = c((e) => e.setConfig), V = c((e) => e.setHassUrl), Q = c((e) => e.setPortalRoot), Y = c((e) => e.setWindowContext), fe = g(async () => i === null ? null : await be(i), [i]), he = g(async () => i === null ? null : await Ie(i), [i]), de = g(async () => i === null ? null : await He(i), [i]), ge = g(async () => i === null ? null : await X(i), [i]);
A(() => {
n && Q(n);
}, [n, Q]), A(() => {
a && Y(a);
}, [a, Y]);
const P = g(() => {
w(null), O(null), p.current = null, S({}), j(null), d(null), _(!1), z(!1), C([]), H(null), s.current = !1, h.current && (h.current(), h.current = null), l.current && (l.current(), l.current = null);
}, [w, H, _, j, O, S, d, z, C]), B = g(async () => {
try {
P(), se(), location && location.reload();
} catch (e) {
console.error("Error:", e), d("Unable to log out!");
}
}, [P, d]), G = g(async () => {
const e = await le(t, o);
e.type === "error" ? (s.current = !1, d(e.error)) : e.type === "failed" ? (s.current = !1, _(!0)) : e.type === "success" && (w(e.auth), O(e.connection), l.current = Le(e.connection, (u) => {
S(u);
}), h.current = xe(e.connection, (u) => {
j(u);
}), e.connection.addEventListener("disconnected", () => {
console.error("Disconnected from Home Assistant, reconnecting..."), R(), P(), G();
}), e.connection.addEventListener("reconnect-error", (u, f) => {
console.error("Reconnection error:", f), P();
}), p.current = e.connection, X(e.connection).then((u) => {
H(u);
}));
}, [t, o, R, d, H, _, w, O, S, j, P]);
A(() => {
V(t);
}, [t, V]);
const ye = g(
(e) => new URL(e, i?.options.auth?.data.hassUrl).toString(),
[i]
);
async function ve(e, u) {
try {
const f = await fetch(`${t}/api${e}`, {
method: "GET",
...u ?? {},
headers: {
Authorization: "Bearer " + i?.options.auth?.accessToken,
"Content-type": "application/json;charset=UTF-8",
...u?.headers ?? {}
}
});
return f.status === 200 ? {
status: "success",
data: await f.json()
} : {
status: "error",
data: f.statusText
};
} catch (f) {
return console.error("API Error:", f), {
status: "error",
data: `API Request failed for endpoint "${e}", follow instructions here: https://shannonhochkins.github.io/ha-component-kit/?path=/docs/core-hooks-usehass-hass-callapi--docs.`
};
}
}
A(() => {
location.hash !== "" && location.hash.replace("#", "") !== v && y(location.hash);
}, [y, v]), A(() => {
function e() {
C(
m.map((u) => u.hash === location.hash.replace("#", "") ? {
...u,
active: !0
} : {
...u,
active: !1
})
), y(location.hash);
}
return window.addEventListener("hashchange", e), () => {
window.removeEventListener("hashchange", e);
};
}, [m, y, C]);
const pe = g(
(e) => {
if (!(m.find((f) => f.hash === e.hash) !== void 0) && typeof window < "u") {
const f = window.location.hash.replace("#", ""), N = f !== "" && f === e.hash;
C([
...m,
{
...e,
active: N
}
]);
}
},
[m, C]
), me = g(
(e) => m.find((f) => f.hash === e) || null,
[m]
), Ee = g(() => E, [E]), Re = g(
async ({
domain: e,
service: u,
serviceData: f,
target: N,
returnResponse: K
}) => {
const Te = typeof N == "string" || ke(N) ? {
entity_id: N
} : N;
if (typeof u != "string")
throw new Error("service must be a string");
if (i && I)
try {
const F = await Ue(
i,
ee(e),
ee(u),
// purposely cast here as we know it's correct
f,
Te,
K
);
return K ? F : void 0;
} catch (F) {
console.log("Error:", F);
}
},
[i, I]
);
A(() => () => {
P();
}, [P]);
const J = We(
async () => {
try {
s.current && P(), s.current = !0, await G();
} catch (e) {
const u = L(e);
d(`Unable to connect to Home Assistant, please check the URL: "${u}"`);
}
},
25,
{
leading: !0,
trailing: !1
}
);
return A(() => {
J();
}, [J]), U ? /* @__PURE__ */ Xe("p", { children: [
"Unable to connect to $",
q(t).hassUrl,
", refresh the page and try again, or ",
/* @__PURE__ */ ne("a", { onClick: B, children: "Logout" }),
"."
] }) : /* @__PURE__ */ ne(
qe.Provider,
{
value: {
useStore: c,
logout: B,
addRoute: pe,
getRoute: me,
getStates: fe,
getServices: he,
getConfig: de,
getUser: ge,
callApi: ve,
getAllEntities: Ee,
// cast here we don't have to redefine all the overloads, might fix later
callService: Re,
joinHassUrl: ye
},
children: b === null ? r(I) : b
}
);
}
export {
lt as F,
ft as H,
Xe as a,
ne as j
};
//# sourceMappingURL=Provider-Cu7u27au.js.map