UNPKG

@hakit/core

Version:

A collection of React hooks and helpers for Home Assistant to easily communicate with the Home Assistant WebSocket API.

581 lines (580 loc) 17.5 kB
import * as x from "react/jsx-runtime"; import * as T from "react"; import { useContext as lt, useState as Ht, useEffect as C, forwardRef as $t, useRef as H, useCallback as v, memo as It, useMemo as xt } from "react"; import Ut from "@emotion/cache"; import { getRegisteredStyles as jt, registerStyles as Dt, insertStyles as kt } from "@emotion/utils"; import { serializeStyles as zt } from "@emotion/serialize"; import Ft from "react-is"; import { getStates as Mt, getServices as Wt, getConfig as qt, getUser as Vt, subscribeConfig as Bt, callService as Qt, subscribeEntities as Yt, createLongLivedTokenAuth as Gt, createConnection as nt, getAuth as Jt, ERR_CANNOT_CONNECT as V, ERR_INVALID_AUTH as ft, ERR_INVALID_HTTPS_TO_HTTP as Kt, ERR_HASS_HOST_REQUIRED as Xt, ERR_CONNECTION_LOST as Zt } from "home-assistant-js-websocket"; import { isEmpty as te, isArray as ee, snakeCase as ot } from "lodash"; import { clearTokens as ht, loadTokens as Y, saveTokens as at } from "./es/HassConnect/token-storage.js"; import { useDebouncedCallback as re } from "use-debounce"; import B from "./es/hooks/useLocale/locales/index.js"; import "./es/utils/light/index.js"; import dt from "@emotion/styled"; import { keyframes as ne } from "@emotion/react"; import { HassContext as pt, useStore as h } from "./es/HassConnect/HassContext.js"; import "@iconify/react"; import "deep-object-diff"; function oe() { const e = lt(pt); if (e === void 0 || te(e)) throw new Error("useHass must be used within a HassProvider, have you wrapped your application in <HassConnect hassUrl={HASS_URL} />?"); return e; } const I = {}; function st(e) { Object.assign(I, e); } function ae(e, r) { const { search: o, replace: n, fallback: s } = r ?? {}; return I[e] ? typeof o == "string" && typeof n == "string" ? I[e].replace(`${o}`, n).trim() : I[e] : s || e; } function Ie() { return I; } const xe = (e, r) => { const { fallback: o = ae("unknown") } = r ?? {}, [n, s] = Ht(o), { getConfig: l } = oe(); return C(() => { (async () => { const i = (await l())?.language, p = B.find((f) => f.code === i); if (p) { const f = await p.fetch(); s(f[e] ?? o); } })(); }, [e, o, l]), n; }; var q, ct; function se() { if (ct) return q; ct = 1; var e = Ft, r = { 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 }, s = { $$typeof: !0, compare: !0, defaultProps: !0, displayName: !0, propTypes: !0, type: !0 }, l = {}; l[e.ForwardRef] = n, l[e.Memo] = s; function a(g) { return e.isMemo(g) ? s : l[g.$$typeof] || r; } var i = Object.defineProperty, p = Object.getOwnPropertyNames, f = Object.getOwnPropertySymbols, U = Object.getOwnPropertyDescriptor, R = Object.getPrototypeOf, S = Object.prototype; function c(g, y, _) { if (typeof y != "string") { if (S) { var b = R(y); b && b !== S && c(g, b, _); } var A = p(y); f && (A = A.concat(f(y))); for (var m = a(g), j = a(y), P = 0; P < A.length; ++P) { var w = A[P]; if (!o[w] && !(_ && _[w]) && !(j && j[w]) && !(m && m[w])) { var L = U(y, w); try { i(g, w, L); } catch { } } } } return g; } return q = c, q; } se(); var ce = function(r) { return r(); }, ie = T.useInsertionEffect ? T.useInsertionEffect : !1, ue = ie || ce, mt = /* @__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__ */ Ut({ key: "css" }) : null ); mt.Provider; var le = function(r) { return /* @__PURE__ */ $t(function(o, n) { var s = lt(mt); return r(o, s, n); }); }, fe = /* @__PURE__ */ T.createContext({}), F = {}.hasOwnProperty, Q = "__EMOTION_TYPE_PLEASE_DO_NOT_USE__", gt = function(r, o) { var n = {}; for (var s in o) F.call(o, s) && (n[s] = o[s]); return n[Q] = r, n; }, he = function(r) { var o = r.cache, n = r.serialized, s = r.isStringTag; return Dt(o, n, s), ue(function() { return kt(o, n, s); }), null; }, de = /* @__PURE__ */ le(function(e, r, o) { var n = e.css; typeof n == "string" && r.registered[n] !== void 0 && (n = r.registered[n]); var s = e[Q], l = [n], a = ""; typeof e.className == "string" ? a = jt(r.registered, l, e.className) : e.className != null && (a = e.className + " "); var i = zt(l, void 0, T.useContext(fe)); a += r.key + "-" + i.name; var p = {}; for (var f in e) F.call(e, f) && f !== "css" && f !== Q && (p[f] = e[f]); return p.className = a, o && (p.ref = o), /* @__PURE__ */ T.createElement(T.Fragment, null, /* @__PURE__ */ T.createElement(he, { cache: r, serialized: i, isStringTag: typeof s == "string" }), /* @__PURE__ */ T.createElement(s, p)); }), yt = de, it = x.Fragment, E = function(r, o, n) { return F.call(o, "css") ? x.jsx(yt, gt(r, o), n) : x.jsx(r, o, n); }, G = function(r, o, n) { return F.call(o, "css") ? x.jsxs(yt, gt(r, o), n) : x.jsxs(r, o, n); }; function $(e, r) { const n = (() => { switch (e) { case ft: return `ERR_INVALID_AUTH: Invalid authentication. ${r ? 'Check your "Long-Lived Access Token".' : ""}`; case V: return "ERR_CANNOT_CONNECT: Unable to connect"; case Zt: return "ERR_CONNECTION_LOST: Lost connection to home assistant."; case Xt: return "ERR_HASS_HOST_REQUIRED: Please enter a Home Assistant URL."; case Kt: return 'ERR_INVALID_HTTPS_TO_HTTP: Cannot connect to Home Assistant instances over "http://".'; default: return null; } })(); return n !== null ? n : e?.error || e?.message || `Unknown Error (${e})`; } function vt() { try { return window.top?.hassConnection; } catch (e) { console.error("Error getting inherited connection", e); return; } } function pe(e, r) { const o = location && location.search.includes("auth_callback=1"), n = !!vt(), s = !!r, l = !!Y(e, !1); switch (!0) { case o: return "auth-callback"; case n: return "inherited-auth"; case s: return "provided-token"; case l: return "saved-tokens"; default: return "user-request"; } } const Et = async (e, r) => { const o = pe(e, r); if (o === "inherited-auth") try { const { auth: a, conn: i } = await vt(); return { type: "success", connection: i, auth: a }; } catch (a) { return { type: "error", error: $(a, r) }; } if (o === "provided-token" && r) try { const a = await Gt(e, r); return { type: "success", connection: await nt({ auth: a }), auth: a }; } catch (a) { return { type: "error", error: $(a, r) }; } const n = { saveTokens: at, loadTokens: () => Promise.resolve(Y(e)) }; if (e && o === "user-request") { if (n.hassUrl = e, 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 (a) { return console.log("Error:", a), { type: "error", error: "Invalid URL" }; } } let s; try { s = await Jt(n); } catch (a) { return a?.error === "invalid_grant" ? (ht(), Et(e, r)) : o === "saved-tokens" && a === V ? { type: "failed", cannotConnect: !0 } : { type: "error", error: $(a, r) }; } finally { location && location.search.includes("auth_callback=1") && history.replaceState(null, "", location.pathname); } let l; try { l = await nt({ auth: s }); } catch (a) { if (o === "saved-tokens") { if (a === V) return { type: "failed", cannotConnect: !0 }; a === ft && at(null); } return { type: "error", error: $(a, r) }; } return { type: "success", connection: l, auth: s }; }; function me({ children: e, hassUrl: r, hassToken: o, locale: n, portalRoot: s, windowContext: l }) { const a = H(null), i = H(!1), p = H(!1), f = h((t) => t.setHash), U = h((t) => t.hash), R = h((t) => t.routes), S = h((t) => t.setRoutes), c = h((t) => t.connection), g = h((t) => t.setConnection), y = H(null), _ = h((t) => t.entities), b = h((t) => t.setEntities), A = h((t) => t.error), m = h((t) => t.setError), j = h((t) => t.cannotConnect), P = h((t) => t.setCannotConnect), w = h((t) => t.setAuth), L = h((t) => t.ready), M = h((t) => t.setReady), N = h((t) => t.setConfig), J = h((t) => t.setHassUrl), K = h((t) => t.setPortalRoot), D = h((t) => t.setLocales), X = h((t) => t.setWindowContext); C(() => { s && K(s); }, [s, K]), C(() => { l && X(l); }, [l, X]); const Z = v(() => { w(null), g(null), y.current = null, b({}), N(null), m(null), P(!1), M(!1), S([]), i.current = !1, a.current && (a.current(), a.current = null); }, [w, P, N, g, b, m, M, S]), tt = v(async () => { try { Z(), ht(), location && location.reload(); } catch (t) { console.log("Error:", t), m("Unable to log out!"); } }, [Z, m]), wt = v(async () => { const t = await Et(r, o); t.type === "error" ? (i.current = !1, m(t.error)) : t.type === "failed" ? (i.current = !1, P(!0)) : t.type === "success" && (w(t.auth), g(t.connection), y.current = t.connection, i.current = !0); }, [r, o, m, w, g, P]); C(() => { J(r); }, [r, J]); const Ct = v(async () => c === null ? null : await Mt(c), [c]), Rt = v(async () => c === null ? null : await Wt(c), [c]), St = v(async () => c === null ? null : await qt(c), [c]), Tt = v(async () => c === null ? null : await Vt(c), [c]), bt = v( (t) => new URL(t, c?.options.auth?.data.hassUrl).toString(), [c] ); async function Pt(t, d) { try { const u = await fetch(`${r}/api${t}`, { method: "GET", ...d ?? {}, headers: { Authorization: "Bearer " + c?.options.auth?.accessToken, "Content-type": "application/json;charset=UTF-8", ...d?.headers ?? {} } }); return u.status === 200 ? { status: "success", data: await u.json() } : { status: "error", data: u.statusText }; } catch (u) { return console.log("Error:", u), { status: "error", data: `API Request failed for endpoint "${t}", follow instructions here: https://shannonhochkins.github.io/ha-component-kit/?path=/docs/core-hooks-usehass-hass-callapi--docs.` }; } } const k = v( async (t) => { const d = B.find(({ code: u }) => u === (n ?? t?.language)); if (d) return await d.fetch(); throw new Error( `Locale "${n ?? t?.language}" not found, available options are "${B.map(({ code: u }) => `${u}`).join(", ")}"` ); }, [n] ); C(() => { n && k(null).then((t) => { st(t), D(t); }).catch((t) => { m(`Error retrieving translations from Home Assistant: ${t?.message ?? t}`); }); }, [n, k, D, m]), C(() => { if (!c || p.current) return; p.current = !0; const t = Bt(c, (d) => { k(d).then((u) => { N(d), st(u), D(u); }).catch((u) => { N(d), m(`Error retrieving translations from Home Assistant: ${u?.message ?? u}`); }); }); return () => { t(); }; }, [c, D, k, N, m]), C(() => { location.hash !== "" && location.hash.replace("#", "") !== U && f(location.hash); }, [f, U]), C(() => { function t() { S( R.map((d) => d.hash === location.hash.replace("#", "") ? { ...d, active: !0 } : { ...d, active: !1 }) ), f(location.hash); } return window.addEventListener("hashchange", t), () => { window.removeEventListener("hashchange", t); }; }, [R, f, S]); const At = v( (t) => { if (!(R.find((u) => u.hash === t.hash) !== void 0) && typeof window < "u") { const u = window.location.hash.replace("#", ""), O = u !== "" && u === t.hash; S([ ...R, { ...t, active: O } ]); } }, [R, S] ), Ot = v( (t) => R.find((u) => u.hash === t) || null, [R] ), _t = v(() => _, [_]), Lt = v( async ({ domain: t, service: d, serviceData: u, target: O, returnResponse: rt }) => { const Nt = typeof O == "string" || ee(O) ? { entity_id: O } : O; if (typeof d != "string") throw new Error("service must be a string"); if (c && L) try { const W = await Qt( c, ot(t), ot(d), // purposely cast here as we know it's correct u, Nt, rt ); return rt ? W : void 0; } catch (W) { console.log("Error:", W); } }, [c, L] ); C(() => { c && a.current === null && (a.current = Yt(c, (t) => { b(t); })); }, [c, b]), C(() => () => { i.current = !1, a.current && (a.current(), a.current = null); }, []); const et = re( async () => { try { if (y.current && !c) { g(y.current), i.current = !0; return; } if (!y.current && c) { y.current = c, i.current = !0; return; } if (i.current) return; i.current = !0, await wt(); } catch (t) { const d = $(t); m(`Unable to connect to Home Assistant, please check the URL: "${d}"`); } }, 100, { leading: !0, trailing: !1 } ); return C(() => { et(); }, [et]), j ? /* @__PURE__ */ G("p", { children: [ "Unable to connect to $", Y(r).hassUrl, ", refresh the page and try again, or ", /* @__PURE__ */ E("a", { onClick: tt, children: "Logout" }), "." ] }) : /* @__PURE__ */ E( pt.Provider, { value: { useStore: h, logout: tt, addRoute: At, getRoute: Ot, getStates: Ct, getServices: Rt, getConfig: St, getUser: Tt, callApi: Pt, getAllEntities: _t, // cast here we don't have to redefine all the overloads, might fix later callService: Lt, joinHassUrl: bt }, children: A === null ? e(L) : A } ); } const z = ne` 0% {stroke-width:0; opacity:0;} 50% {stroke-width:5; opacity:1;} 100% {stroke-width:0; opacity:0;} `; function ge({ className: e }) { return /* @__PURE__ */ E("div", { className: e, children: /* @__PURE__ */ G("svg", { children: [ /* @__PURE__ */ E("path", { d: "m 12.5,20 15,0 0,0 -15,0 z" }), /* @__PURE__ */ E("path", { d: "m 32.5,20 15,0 0,0 -15,0 z" }), /* @__PURE__ */ E("path", { d: "m 52.5,20 15,0 0,0 -15,0 z" }), /* @__PURE__ */ E("path", { d: "m 72.5,20 15,0 0,0 -15,0 z" }) ] }) }); } const ye = dt(ge)` position: fixed; inset: 0; background-color: #1a1a1a; svg { position: absolute; top: 50%; left: 50%; width: 6.25em; height: 3.125em; margin: -1.562em 0 0 -3.125em; path { fill: none; stroke: #f0c039; opacity: 0; } path:nth-of-type(1) { animation: ${z} 1s ease-in-out 0s infinite alternate; } path:nth-of-type(2) { animation: ${z} 1s ease-in-out 0.1s infinite alternate; } path:nth-of-type(3) { animation: ${z} 1s ease-in-out 0.2s infinite alternate; } path:nth-of-type(4) { animation: ${z} 1s ease-in-out 0.3s infinite alternate; } } `, ut = dt.div` width: 100%; height: 100%; `, Ue = It(function({ children: r, hassUrl: o, hassToken: n, loading: s = /* @__PURE__ */ E(ye, {}), onReady: l, options: a = {} }) { const i = H(!1), p = xt(() => { try { return new URL(o).origin; } catch (f) { return console.log("Error:", f), null; } }, [o]); return !p || p === "null" || p === null ? /* @__PURE__ */ E(it, { children: "Provide the hassUrl prop with a valid url to your home assistant instance." }) : /* @__PURE__ */ E(me, { hassUrl: p, hassToken: n, ...a, children: (f) => /* @__PURE__ */ E(it, { children: f ? /* @__PURE__ */ G(ut, { children: [ l && !i.current && (l(), i.current = !0, null), r ] }) : /* @__PURE__ */ E(ut, { children: s }) }) }); }); export { Ue as H, xe as a, Ie as b, st as c, me as d, E as j, ae as l, oe as u }; //# sourceMappingURL=index-BPKUNssF.js.map