UNPKG

@hakit/core

Version:

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

39 lines (38 loc) 12.9 kB
"use strict";const Pe=require("react/jsx-runtime"),o=require("react"),Ae=require("@emotion/cache"),J=require("@emotion/utils"),He=require("@emotion/serialize"),Le=require("react-is"),p=require("home-assistant-js-websocket"),z=require("lodash"),P=require("./cjs/HassConnect/token-storage.js"),ke=require("use-debounce"),B=require("./cjs/hooks/useLocale/locales/index.js");require("./cjs/utils/light/index.js");const ce=require("@emotion/styled"),Ie=require("@emotion/react"),f=require("./cjs/HassConnect/HassContext.js");require("@iconify/react");require("deep-object-diff");function ie(t){const n=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const s in t)if(s!=="default"){const r=Object.getOwnPropertyDescriptor(t,s);Object.defineProperty(n,s,r.get?r:{enumerable:!0,get:()=>t[s]})}}return n.default=t,Object.freeze(n)}const x=ie(Pe),b=ie(o);function ue(){const t=o.useContext(f.HassContext);if(t===void 0||z.isEmpty(t))throw new Error("useHass must be used within a HassProvider, have you wrapped your application in <HassConnect hassUrl={HASS_URL} />?");return t}const I={};function Q(t){Object.assign(I,t)}function le(t,n){const{search:s,replace:r,fallback:c}=n??{};return I[t]?typeof s=="string"&&typeof r=="string"?I[t].replace(`${s}`,r).trim():I[t]:c||t}function xe(){return I}const je=(t,n)=>{const{fallback:s=le("unknown")}=n??{},[r,c]=o.useState(s),{getConfig:h}=ue();return o.useEffect(()=>{(async()=>{const u=(await h())?.language,y=B.default.find(d=>d.code===u);if(y){const d=await y.fetch();c(d[t]??s)}})()},[t,s,h]),r};var V,se;function Ue(){if(se)return V;se=1;var t=Le,n={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},s={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},r={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},c={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},h={};h[t.ForwardRef]=r,h[t.Memo]=c;function a(m){return t.isMemo(m)?c:h[m.$$typeof]||n}var u=Object.defineProperty,y=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,j=Object.getOwnPropertyDescriptor,R=Object.getPrototypeOf,T=Object.prototype;function i(m,E,A){if(typeof E!="string"){if(T){var w=R(E);w&&w!==T&&i(m,w,A)}var O=y(E);d&&(O=O.concat(d(E)));for(var v=a(m),U=a(E),_=0;_<O.length;++_){var S=O[_];if(!s[S]&&!(A&&A[S])&&!(U&&U[S])&&!(v&&v[S])){var H=j(E,S);try{u(m,S,H)}catch{}}}}return m}return V=i,V}Ue();var $e=function(n){return n()},qe=b.useInsertionEffect?b.useInsertionEffect:!1,De=qe||$e,fe=b.createContext(typeof HTMLElement<"u"?Ae({key:"css"}):null);fe.Provider;var ze=function(n){return o.forwardRef(function(s,r){var c=o.useContext(fe);return n(s,c,r)})},Fe=b.createContext({}),F={}.hasOwnProperty,X="__EMOTION_TYPE_PLEASE_DO_NOT_USE__",he=function(n,s){var r={};for(var c in s)F.call(s,c)&&(r[c]=s[c]);return r[X]=n,r},Me=function(n){var s=n.cache,r=n.serialized,c=n.isStringTag;return J.registerStyles(s,r,c),De(function(){return J.insertStyles(s,r,c)}),null},We=ze(function(t,n,s){var r=t.css;typeof r=="string"&&n.registered[r]!==void 0&&(r=n.registered[r]);var c=t[X],h=[r],a="";typeof t.className=="string"?a=J.getRegisteredStyles(n.registered,h,t.className):t.className!=null&&(a=t.className+" ");var u=He.serializeStyles(h,void 0,b.useContext(Fe));a+=n.key+"-"+u.name;var y={};for(var d in t)F.call(t,d)&&d!=="css"&&d!==X&&(y[d]=t[d]);return y.className=a,s&&(y.ref=s),b.createElement(b.Fragment,null,b.createElement(Me,{cache:n,serialized:u,isStringTag:typeof c=="string"}),b.createElement(c,y))}),de=We,ae=x.Fragment,C=function(n,s,r){return F.call(s,"css")?x.jsx(de,he(n,s),r):x.jsx(n,s,r)},Y=function(n,s,r){return F.call(s,"css")?x.jsxs(de,he(n,s),r):x.jsxs(n,s,r)};function k(t,n){const r=(()=>{switch(t){case p.ERR_INVALID_AUTH:return`ERR_INVALID_AUTH: Invalid authentication. ${n?'Check your "Long-Lived Access Token".':""}`;case p.ERR_CANNOT_CONNECT:return"ERR_CANNOT_CONNECT: Unable to connect";case p.ERR_CONNECTION_LOST:return"ERR_CONNECTION_LOST: Lost connection to home assistant.";case p.ERR_HASS_HOST_REQUIRED:return"ERR_HASS_HOST_REQUIRED: Please enter a Home Assistant URL.";case p.ERR_INVALID_HTTPS_TO_HTTP:return'ERR_INVALID_HTTPS_TO_HTTP: Cannot connect to Home Assistant instances over "http://".';default:return null}})();return r!==null?r:t?.error||t?.message||`Unknown Error (${t})`}function ge(){try{return window.top?.hassConnection}catch(t){console.error("Error getting inherited connection",t);return}}function Ve(t,n){const s=location&&location.search.includes("auth_callback=1"),r=!!ge(),c=!!n,h=!!P.loadTokens(t,!1);switch(!0){case s:return"auth-callback";case r:return"inherited-auth";case c:return"provided-token";case h:return"saved-tokens";default:return"user-request"}}const pe=async(t,n)=>{const s=Ve(t,n);if(s==="inherited-auth")try{const{auth:a,conn:u}=await ge();return{type:"success",connection:u,auth:a}}catch(a){return{type:"error",error:k(a,n)}}if(s==="provided-token"&&n)try{const a=await p.createLongLivedTokenAuth(t,n);return{type:"success",connection:await p.createConnection({auth:a}),auth:a}}catch(a){return{type:"error",error:k(a,n)}}const r={saveTokens:P.saveTokens,loadTokens:()=>Promise.resolve(P.loadTokens(t))};if(t&&s==="user-request"){if(r.hassUrl=t,r.hassUrl==="")return{type:"error",error:"Please enter a Home Assistant URL."};if(r.hassUrl.indexOf("://")===-1)return{type:"error",error:"Please enter your full URL, including the protocol part (https://)."};try{new URL(r.hassUrl)}catch(a){return console.log("Error:",a),{type:"error",error:"Invalid URL"}}}let c;try{c=await p.getAuth(r)}catch(a){return a?.error==="invalid_grant"?(P.clearTokens(),pe(t,n)):s==="saved-tokens"&&a===p.ERR_CANNOT_CONNECT?{type:"failed",cannotConnect:!0}:{type:"error",error:k(a,n)}}finally{location&&location.search.includes("auth_callback=1")&&history.replaceState(null,"",location.pathname)}let h;try{h=await p.createConnection({auth:c})}catch(a){if(s==="saved-tokens"){if(a===p.ERR_CANNOT_CONNECT)return{type:"failed",cannotConnect:!0};a===p.ERR_INVALID_AUTH&&P.saveTokens(null)}return{type:"error",error:k(a,n)}}return{type:"success",connection:h,auth:c}};function ye({children:t,hassUrl:n,hassToken:s,locale:r,portalRoot:c,windowContext:h}){const a=o.useRef(null),u=o.useRef(!1),y=o.useRef(!1),d=f.useStore(e=>e.setHash),j=f.useStore(e=>e.hash),R=f.useStore(e=>e.routes),T=f.useStore(e=>e.setRoutes),i=f.useStore(e=>e.connection),m=f.useStore(e=>e.setConnection),E=o.useRef(null),A=f.useStore(e=>e.entities),w=f.useStore(e=>e.setEntities),O=f.useStore(e=>e.error),v=f.useStore(e=>e.setError),U=f.useStore(e=>e.cannotConnect),_=f.useStore(e=>e.setCannotConnect),S=f.useStore(e=>e.setAuth),H=f.useStore(e=>e.ready),M=f.useStore(e=>e.setReady),L=f.useStore(e=>e.setConfig),G=f.useStore(e=>e.setHassUrl),K=f.useStore(e=>e.setPortalRoot),$=f.useStore(e=>e.setLocales),Z=f.useStore(e=>e.setWindowContext);o.useEffect(()=>{c&&K(c)},[c,K]),o.useEffect(()=>{h&&Z(h)},[h,Z]);const ee=o.useCallback(()=>{S(null),m(null),E.current=null,w({}),L(null),v(null),_(!1),M(!1),T([]),u.current=!1,a.current&&(a.current(),a.current=null)},[S,_,L,m,w,v,M,T]),te=o.useCallback(async()=>{try{ee(),P.clearTokens(),location&&location.reload()}catch(e){console.log("Error:",e),v("Unable to log out!")}},[ee,v]),ve=o.useCallback(async()=>{const e=await pe(n,s);e.type==="error"?(u.current=!1,v(e.error)):e.type==="failed"?(u.current=!1,_(!0)):e.type==="success"&&(S(e.auth),m(e.connection),E.current=e.connection,u.current=!0)},[n,s,v,S,m,_]);o.useEffect(()=>{G(n)},[n,G]);const me=o.useCallback(async()=>i===null?null:await p.getStates(i),[i]),Ee=o.useCallback(async()=>i===null?null:await p.getServices(i),[i]),Ce=o.useCallback(async()=>i===null?null:await p.getConfig(i),[i]),Se=o.useCallback(async()=>i===null?null:await p.getUser(i),[i]),Re=o.useCallback(e=>new URL(e,i?.options.auth?.data.hassUrl).toString(),[i]);async function Te(e,g){try{const l=await fetch(`${n}/api${e}`,{method:"GET",...g??{},headers:{Authorization:"Bearer "+i?.options.auth?.accessToken,"Content-type":"application/json;charset=UTF-8",...g?.headers??{}}});return l.status===200?{status:"success",data:await l.json()}:{status:"error",data:l.statusText}}catch(l){return console.log("Error:",l),{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.`}}}const q=o.useCallback(async e=>{const g=B.default.find(({code:l})=>l===(r??e?.language));if(g)return await g.fetch();throw new Error(`Locale "${r??e?.language}" not found, available options are "${B.default.map(({code:l})=>`${l}`).join(", ")}"`)},[r]);o.useEffect(()=>{r&&q(null).then(e=>{Q(e),$(e)}).catch(e=>{v(`Error retrieving translations from Home Assistant: ${e?.message??e}`)})},[r,q,$,v]),o.useEffect(()=>{if(!i||y.current)return;y.current=!0;const e=p.subscribeConfig(i,g=>{q(g).then(l=>{L(g),Q(l),$(l)}).catch(l=>{L(g),v(`Error retrieving translations from Home Assistant: ${l?.message??l}`)})});return()=>{e()}},[i,$,q,L,v]),o.useEffect(()=>{location.hash!==""&&location.hash.replace("#","")!==j&&d(location.hash)},[d,j]),o.useEffect(()=>{function e(){T(R.map(g=>g.hash===location.hash.replace("#","")?{...g,active:!0}:{...g,active:!1})),d(location.hash)}return window.addEventListener("hashchange",e),()=>{window.removeEventListener("hashchange",e)}},[R,d,T]);const be=o.useCallback(e=>{if(!(R.find(l=>l.hash===e.hash)!==void 0)&&typeof window<"u"){const l=window.location.hash.replace("#",""),N=l!==""&&l===e.hash;T([...R,{...e,active:N}])}},[R,T]),we=o.useCallback(e=>R.find(l=>l.hash===e)||null,[R]),_e=o.useCallback(()=>A,[A]),Oe=o.useCallback(async({domain:e,service:g,serviceData:l,target:N,returnResponse:re})=>{const Ne=typeof N=="string"||z.isArray(N)?{entity_id:N}:N;if(typeof g!="string")throw new Error("service must be a string");if(i&&H)try{const W=await p.callService(i,z.snakeCase(e),z.snakeCase(g),l,Ne,re);return re?W:void 0}catch(W){console.log("Error:",W)}},[i,H]);o.useEffect(()=>{i&&a.current===null&&(a.current=p.subscribeEntities(i,e=>{w(e)}))},[i,w]),o.useEffect(()=>()=>{u.current=!1,a.current&&(a.current(),a.current=null)},[]);const ne=ke.useDebouncedCallback(async()=>{try{if(E.current&&!i){m(E.current),u.current=!0;return}if(!E.current&&i){E.current=i,u.current=!0;return}if(u.current)return;u.current=!0,await ve()}catch(e){const g=k(e);v(`Unable to connect to Home Assistant, please check the URL: "${g}"`)}},100,{leading:!0,trailing:!1});return o.useEffect(()=>{ne()},[ne]),U?Y("p",{children:["Unable to connect to $",P.loadTokens(n).hassUrl,", refresh the page and try again, or ",C("a",{onClick:te,children:"Logout"}),"."]}):C(f.HassContext.Provider,{value:{useStore:f.useStore,logout:te,addRoute:be,getRoute:we,getStates:me,getServices:Ee,getConfig:Ce,getUser:Se,callApi:Te,getAllEntities:_e,callService:Oe,joinHassUrl:Re},children:O===null?t(H):O})}const D=Ie.keyframes` 0% {stroke-width:0; opacity:0;} 50% {stroke-width:5; opacity:1;} 100% {stroke-width:0; opacity:0;} `;function Je({className:t}){return C("div",{className:t,children:Y("svg",{children:[C("path",{d:"m 12.5,20 15,0 0,0 -15,0 z"}),C("path",{d:"m 32.5,20 15,0 0,0 -15,0 z"}),C("path",{d:"m 52.5,20 15,0 0,0 -15,0 z"}),C("path",{d:"m 72.5,20 15,0 0,0 -15,0 z"})]})})}const Be=ce(Je)` 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: ${D} 1s ease-in-out 0s infinite alternate; } path:nth-of-type(2) { animation: ${D} 1s ease-in-out 0.1s infinite alternate; } path:nth-of-type(3) { animation: ${D} 1s ease-in-out 0.2s infinite alternate; } path:nth-of-type(4) { animation: ${D} 1s ease-in-out 0.3s infinite alternate; } } `,oe=ce.div` width: 100%; height: 100%; `,Qe=o.memo(function({children:n,hassUrl:s,hassToken:r,loading:c=C(Be,{}),onReady:h,options:a={}}){const u=o.useRef(!1),y=o.useMemo(()=>{try{return new URL(s).origin}catch(d){return console.log("Error:",d),null}},[s]);return!y||y==="null"||y===null?C(ae,{children:"Provide the hassUrl prop with a valid url to your home assistant instance."}):C(ye,{hassUrl:y,hassToken:r,...a,children:d=>C(ae,{children:d?Y(oe,{children:[h&&!u.current&&(h(),u.current=!0,null),n]}):C(oe,{children:c})})})});exports.HassConnect=Qe;exports.HassProvider=ye;exports.jsx=C;exports.localize=le;exports.updateLocales=Q;exports.useHass=ue;exports.useLocale=je;exports.useLocales=xe; //# sourceMappingURL=index-BbkMZwLg.cjs.map