@hakit/core
Version:
A collection of React hooks and helpers for Home Assistant to easily communicate with the Home Assistant WebSocket API.
39 lines (36 loc) • 5.76 kB
JavaScript
;Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const v=require("home-assistant-js-websocket"),K=require("ws"),h=require("lodash"),W=require("node:fs"),Q=require("prettier"),X="auth_required",Z="auth_invalid",B="auth_ok",ee=1,L=2;function ne(s){const e=s.wsUrl;console.info("[Auth phase] Initializing WebSocket connection to Home Assistant",e);function i(r,c,a){console.info(`[Auth Phase] Connecting to Home Assistant... Tries left: ${r}`,e);const n=new K(e,{rejectUnauthorized:!1});let l=!1;const d=o=>{let t;o&&o.code&&o.code!==1e3&&(t=`WebSocket connection to Home Assistant closed with code ${o.code} and reason ${o.reason}`),f(t)},m=o=>{n.removeEventListener("close",d);let t="Disconnected from Home Assistant with a WebSocket error";o.message&&(t+=` with message: ${o.message}`),f(t)},f=o=>{if(o&&console.info(`WebSocket Connection to Home Assistant closed with an error: ${o}`),l){a(L);return}if(r===0){a(ee);return}const t=r===-1?-1:r-1;setTimeout(()=>i(t,c,a),1e3)},u=async()=>{try{s.expired&&await s.refreshAccessToken(),n.send(JSON.stringify({type:"auth",access_token:s.accessToken}))}catch(o){l=o===L,n.close()}},p=o=>{const t=JSON.parse(o.data);switch(console.info(`[Auth phase] Received a message of type ${t.type}`,t),t.type){case Z:l=!0,n.close();break;case B:n.removeEventListener("open",u),n.removeEventListener("message",p),n.removeEventListener("close",d),n.removeEventListener("error",m),n.haVersion=t.ha_version,c(n);break;default:t.type!==X&&console.info("[Auth phase] Unhandled message",t)}};n.addEventListener("open",u),n.addEventListener("message",p),n.addEventListener("close",d),n.addEventListener("error",m)}return new Promise((r,c)=>i(3,r,c))}async function te(s,e){try{const i=v.createLongLivedTokenAuth(s,e),r=await v.createConnection({auth:i,createSocket:()=>ne(i)}),c=await v.getServices(r),a=await v.getStates(r);return r.close(),{services:c,states:a}}catch(i){throw console.error("err",i),new Error("Failed to connect to Home Assistant")}}const oe="supported-types.d.ts",b={hs_color:"[number, number]",rgb_color:"[number, number, number]",rgbw_color:"[number, number, number, number]",rgbww_color:"[number, number, number, number, number]",group_members:"string[]",media_content_id:"string | number",color_temp_kelvin:"number",white:"boolean",color_temp:"number",xy_color:"[number, number]"},se=s=>{if(!s)return"object";const e=Object.keys(s);if(e.includes("number"))return"number";if(e.includes("object"))return"object";if(e.includes("duration"))return`{
hours?: number;
days?: number;
minutes?: number;
seconds?: number;
}`;if(["text","entity","datetime","time","date","addon","backup_location","icon","conversation_agent","device","theme"].some(c=>e.includes(c)))return"string";if(e.includes("boolean"))return"boolean";if(e.includes("select")){const c=s?.select?.options;return!h.isArray(c)||c.length===0?"unknown":`${c.map(a=>`'${typeof a=="string"?a:a.value}'`).join(" | ")}`}return"unknown"};function C(s){return`${s}`.replace(/"/g,"'").replace(/[\n\r]+/g," ")}const re=(s,{domainWhitelist:e=[],domainBlacklist:i=[],serviceWhitelist:r=[],serviceBlacklist:c=[]})=>Object.entries(s).map(([n,l])=>{const d=h.camelCase(n);if(i.length>0&&(i.includes(d)||i.includes(n))||e.length>0&&(!e.includes(d)||!e.includes(n)))return"";const m=Object.entries(l).map(([u,{fields:p,description:o}])=>{const t=h.camelCase(u);if(c.length>0&&(c.includes(t)||c.includes(u))||r.length>0&&(!r.includes(t)||!r.includes(u)))return"";function g(H){return Object.entries(H).map(([y,{selector:w,example:E,description:P,...A}])=>{const U=A.required??!1,j=`${n}.${u}.${y}`,O=`${u}.${y}`,k=y,x=j in b?b[j]:void 0,I=O in b?b[O]:void 0,q=k in b?b[k]:void 0,F=w,D=x||I||q,R=typeof D=="string"?D:se(F);let $="";if(h.isObject(w)){const G=["select","entity","theme","constant","text","device"];$=Object.entries(F||{}).filter(([_,T])=>_&&h.isObject(T)&&!G.includes(_)).map(([_,T])=>` ${_}: ${Object.entries(T||{}).map(([J,V])=>`${J}: ${V}`).join(", ")}`).join(", "),$=$?` @constraints ${$}`:""}const Y=E?` @example ${E}`:"",z=y==="advanced_fields",M=`${P??""}${Y??""}${$}`;return z&&"fields"in A?g(A.fields).join(`
`):`//${M?C(` ${M}`):""}
${y}${U?"":"?"}: ${R};`})}const S=g(p),N=`${Object.keys(p).length===0?"object":`{${S.join(`
`)}}`}`;return`// ${C(o)}
${t}: ServiceFunction<object, T, ${N}>;
`}).join("");return`${d}: {
${m}
}
`}).join(""),ce=s=>s.map(e=>`'${e.entity_id}'`).join(" | ");async function ie({url:s,token:e,outDir:i,filename:r=oe,domainWhitelist:c=[],domainBlacklist:a=[],serviceWhitelist:n=[],serviceBlacklist:l=[],custom:d=!0,prettier:m}){if(!s||!e)throw new Error("Missing url or token arguments");const f=`
// this is an auto generated file, do not change this manually
`,{states:u,services:p}=await te(s,e),o=await re(p,{domainWhitelist:c,domainBlacklist:a,serviceWhitelist:n,serviceBlacklist:l}),t=d?`
${f}
import { ServiceFunction, ServiceFunctionTypes } from "@hakit/core";
declare module '@hakit/core' {
export interface CustomSupportedServices<T extends ServiceFunctionTypes = "target"> {
${o}
}
export interface CustomEntityNameContainer {
names: ${ce(u)};
}
}
`:`
${f}
import type { ServiceFunctionTypes, ServiceFunction } from "./";
export interface DefaultServices<T extends ServiceFunctionTypes = "target"> {
${o}
}
`,g=i||process.cwd(),S=m?.disable?t:await Q.format(t,{parser:"typescript",...m?.options});W.writeFileSync(`${g}/${r}`,S),console.info(`Succesfully generated types: ${g}/${r}
`),console.info(`IMPORTANT: Don't forget to add the "${r}" file to your tsconfig.app.json include array
`)}exports.typeSync=ie;
//# sourceMappingURL=index.cjs.map