/**
 * Create a web socket connection with a Home Assistant instance.
 */ import { ERR_INVALID_AUTH , ERR_CANNOT_CONNECT , ERR_HASS_HOST_REQUIRED , } from "./errors.js" ; import { Error } from "./types.js" ; import type { ConnectionOptions } from "./connection.js" ; import * as messages from "./messages.js" ; const DEBUG = false ; export const MSG_TYPE_AUTH_REQUIRED = "auth_required" ; export const MSG_TYPE_AUTH_INVALID = "auth_invalid" ; export const MSG_TYPE_AUTH_OK = "auth_ok" ; export interface HaWebSocket extends WebSocket { haVersion : string ; } export function createSocket ( options ) { if ( ! options . auth ) { throw ERR_HASS_HOST_REQUIRED ; } const auth = options . auth ; let authRefreshTask = auth . expired ? auth . refreshAccessToken ( ) . then ( ( ) => { authRefreshTask = undefined ; } , ( ) => { authRefreshTask = undefined ; } ) : undefined ; const url = auth . wsUrl ; if ( DEBUG ) { console . log ( "[Auth phase] Initializing" , url ) ; } function connect ( triesLeft , promResolve , promReject ) { if ( DEBUG ) { console . log ( "[Auth Phase] New connection" , url ) ; } const socket = new WebSocket ( url ) as HaWebSocket ; let invalidAuth = false ; const closeMessage = ( ) => { socket . removeEventListener ( "close" , closeMessage ) ; if ( invalidAuth ) { promReject ( ERR_INVALID_AUTH ) ; return ; } if ( triesLeft === 0 ) { promReject ( ERR_CANNOT_CONNECT ) ; return ; } const newTries = triesLeft === - 1 ? - 1 : triesLeft - 1 ; setTimeout ( ( ) => connect ( newTries , promResolve , promReject ) , 1000 ) ; } ; const handleOpen = async ( event ) => { try { if ( auth . expired ) { await ( authRefreshTask ? authRefreshTask : auth . refreshAccessToken ( ) ) ; } socket . send ( JSON . stringify ( messages . auth ( auth . accessToken ) ) ) ; } catch ( err ) { invalidAuth = err === ERR_INVALID_AUTH ; socket . close ( ) ; } } ; const handleMessage = async ( event ) => { const message = JSON . parse ( event . data ) ; if ( DEBUG ) { console . log ( "[Auth phase] Received" , message ) ; } switch ( message . type ) { case MSG_TYPE_AUTH_INVALID : invalidAuth = true ; socket . close ( ) ; break ; case MSG_TYPE_AUTH_OK : socket . removeEventListener ( "open" , handleOpen ) ; socket . removeEventListener ( "message" , handleMessage ) ; socket . removeEventListener ( "close" , closeMessage ) ; socket . removeEventListener ( "error" , closeMessage ) ; socket . haVersion = message . ha_version ; promResolve ( socket ) ; break ; default : if ( DEBUG ) { if ( message . type !== MSG_TYPE_AUTH_REQUIRED ) { console . warn ( "[Auth phase] Unhandled message" , message ) ; } } } } ; socket . addEventListener ( "open" , handleOpen ) ; socket . addEventListener ( "message" , handleMessage ) ; socket . addEventListener ( "close" , closeMessage ) ; socket . addEventListener ( "error" , closeMessage ) ; } return new Promise ( ( resolve , reject ) => connect ( options . setupRetry , resolve , reject ) ) ; }