UNPKG

@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
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