11import { useEffect , useCallback , useRef } from "react" ;
22// types
3- import type { Connection , HassConfig , getAuthOptions as AuthOptions , Auth , UnsubscribeFunc } from "home-assistant-js-websocket" ;
3+ import type { Connection , getAuthOptions as AuthOptions , Auth , UnsubscribeFunc } from "home-assistant-js-websocket" ;
44// methods
55import {
66 getAuth ,
@@ -23,8 +23,6 @@ import { isArray, snakeCase } from "lodash";
2323import { SnakeOrCamelDomains , DomainService , Locales , CallServiceArgs , Route , ServiceResponse } from "@typings" ;
2424import { saveTokens , loadTokens , clearTokens } from "./token-storage" ;
2525import { useDebouncedCallback } from "use-debounce" ;
26- import locales from "../hooks/useLocale/locales" ;
27- import { updateLocales } from "../hooks/useLocale" ;
2826import { HassContext , type HassContextProps , useStore } from "./HassContext" ;
2927
3028export interface HassProviderProps {
@@ -179,7 +177,7 @@ const tryConnection = async (hassUrl: string, hassToken?: string): Promise<Conne
179177 try {
180178 new URL ( options . hassUrl ) ;
181179 } catch ( err : unknown ) {
182- console . log ( "Error:" , err ) ;
180+ console . error ( "Error:" , err ) ;
183181 return {
184182 type : "error" ,
185183 error : "Invalid URL" ,
@@ -246,10 +244,10 @@ const tryConnection = async (hassUrl: string, hassToken?: string): Promise<Conne
246244 } ;
247245} ;
248246
249- export function HassProvider ( { children, hassUrl, hassToken, locale , portalRoot, windowContext } : HassProviderProps ) {
247+ export function HassProvider ( { children, hassUrl, hassToken, portalRoot, windowContext } : HassProviderProps ) {
250248 const entityUnsubscribe = useRef < UnsubscribeFunc | null > ( null ) ;
251249 const authenticated = useRef ( false ) ;
252- const subscribedConfig = useRef ( false ) ;
250+ const configUnsubscribe = useRef < UnsubscribeFunc | null > ( null ) ;
253251 const setHash = useStore ( ( store ) => store . setHash ) ;
254252 const _hash = useStore ( ( store ) => store . hash ) ;
255253 const routes = useStore ( ( store ) => store . routes ) ;
@@ -264,12 +262,13 @@ export function HassProvider({ children, hassUrl, hassToken, locale, portalRoot,
264262 const cannotConnect = useStore ( ( store ) => store . cannotConnect ) ;
265263 const setCannotConnect = useStore ( ( store ) => store . setCannotConnect ) ;
266264 const setAuth = useStore ( ( store ) => store . setAuth ) ;
265+ const triggerOnDisconnect = useStore ( ( store ) => store . triggerOnDisconnect ) ;
266+ // ready is set internally in the store when we have entities (setEntities does this)
267267 const ready = useStore ( ( store ) => store . ready ) ;
268268 const setReady = useStore ( ( store ) => store . setReady ) ;
269269 const setConfig = useStore ( ( store ) => store . setConfig ) ;
270270 const setHassUrl = useStore ( ( store ) => store . setHassUrl ) ;
271271 const setPortalRoot = useStore ( ( store ) => store . setPortalRoot ) ;
272- const setLocales = useStore ( ( store ) => store . setLocales ) ;
273272 const setWindowContext = useStore ( ( store ) => store . setWindowContext ) ;
274273
275274 useEffect ( ( ) => {
@@ -292,6 +291,10 @@ export function HassProvider({ children, hassUrl, hassToken, locale, portalRoot,
292291 setReady ( false ) ;
293292 setRoutes ( [ ] ) ;
294293 authenticated . current = false ;
294+ if ( configUnsubscribe . current ) {
295+ configUnsubscribe . current ( ) ;
296+ configUnsubscribe . current = null ;
297+ }
295298 if ( entityUnsubscribe . current ) {
296299 entityUnsubscribe . current ( ) ;
297300 entityUnsubscribe . current = null ;
@@ -304,7 +307,7 @@ export function HassProvider({ children, hassUrl, hassToken, locale, portalRoot,
304307 clearTokens ( ) ;
305308 if ( location ) location . reload ( ) ;
306309 } catch ( err : unknown ) {
307- console . log ( "Error:" , err ) ;
310+ console . error ( "Error:" , err ) ;
308311 setError ( "Unable to log out!" ) ;
309312 }
310313 } , [ reset , setError ] ) ;
@@ -323,10 +326,28 @@ export function HassProvider({ children, hassUrl, hassToken, locale, portalRoot,
323326 setAuth ( connectionResponse . auth ) ;
324327 // store the connection to pass to the provider
325328 setConnection ( connectionResponse . connection ) ;
329+ entityUnsubscribe . current = subscribeEntities ( connectionResponse . connection , ( $entities ) => {
330+ setEntities ( $entities ) ;
331+ } ) ;
332+ configUnsubscribe . current = subscribeConfig ( connectionResponse . connection , ( newConfig ) => {
333+ setConfig ( newConfig ) ;
334+ } ) ;
335+ connectionResponse . connection . addEventListener ( "disconnected" , ( ) => {
336+ console . error ( "Disconnected from Home Assistant, reconnecting..." ) ;
337+ triggerOnDisconnect ( ) ;
338+ // on disconnection, reset local state
339+ reset ( ) ;
340+ // try to reconnect
341+ handleConnect ( ) ;
342+ } ) ;
343+ connectionResponse . connection . addEventListener ( "reconnect-error" , ( _ , eventData ) => {
344+ console . error ( "Reconnection error:" , eventData ) ;
345+ // on connection error, reset local state
346+ reset ( ) ;
347+ } ) ;
326348 _connectionRef . current = connectionResponse . connection ;
327- authenticated . current = true ;
328349 }
329- } , [ hassUrl , hassToken , setError , setAuth , setConnection , setCannotConnect ] ) ;
350+ } , [ hassUrl , hassToken , triggerOnDisconnect , setError , setCannotConnect , setAuth , setConnection , setEntities , setConfig , reset ] ) ;
330351
331352 useEffect ( ( ) => {
332353 setHassUrl ( hassUrl ) ;
@@ -379,64 +400,14 @@ export function HassProvider({ children, hassUrl, hassToken, locale, portalRoot,
379400 data : response . statusText ,
380401 } ;
381402 } catch ( e ) {
382- console . log ( " Error:", e ) ;
403+ console . error ( "API Error:", e ) ;
383404 return {
384405 status : "error" ,
385406 data : `API Request failed for endpoint "${ endpoint } ", follow instructions here: https://shannonhochkins.github.io/ha-component-kit/?path=/docs/core-hooks-usehass-hass-callapi--docs.` ,
386407 } ;
387408 }
388409 }
389410
390- const fetchLocale = useCallback (
391- async ( config : HassConfig | null ) : Promise < Record < string , string > > => {
392- const match = locales . find ( ( { code } ) => code === ( locale ?? config ?. language ) ) ;
393- if ( ! match ) {
394- throw new Error (
395- `Locale "${ locale ?? config ?. language } " not found, available options are "${ locales . map ( ( { code } ) => `${ code } ` ) . join ( ", " ) } "` ,
396- ) ;
397- } else {
398- return await match . fetch ( ) ;
399- }
400- } ,
401- [ locale ] ,
402- ) ;
403-
404- useEffect ( ( ) => {
405- if ( ! locale ) return ;
406- // purposely sending null for the config object as we're fetching a different language specified by the user
407- fetchLocale ( null )
408- . then ( ( locales ) => {
409- updateLocales ( locales ) ;
410- setLocales ( locales ) ;
411- } )
412- . catch ( ( e ) => {
413- setError ( `Error retrieving translations from Home Assistant: ${ e ?. message ?? e } ` ) ;
414- } ) ;
415- } , [ locale , fetchLocale , setLocales , setError ] ) ;
416-
417- useEffect ( ( ) => {
418- if ( ! connection || subscribedConfig . current ) return ;
419- subscribedConfig . current = true ;
420- // Subscribe to config updates
421- const unsubscribe = subscribeConfig ( connection , ( newConfig ) => {
422- fetchLocale ( newConfig )
423- . then ( ( locales ) => {
424- // purposely setting config here to delay the rendering process of the application until locales are retrieved
425- setConfig ( newConfig ) ;
426- updateLocales ( locales ) ;
427- setLocales ( locales ) ;
428- } )
429- . catch ( ( e ) => {
430- setConfig ( newConfig ) ;
431- setError ( `Error retrieving translations from Home Assistant: ${ e ?. message ?? e } ` ) ;
432- } ) ;
433- } ) ;
434- // Cleanup function to unsubscribe on unmount
435- return ( ) => {
436- unsubscribe ( ) ;
437- } ;
438- } , [ connection , setLocales , fetchLocale , setConfig , setError ] ) ;
439-
440411 useEffect ( ( ) => {
441412 if ( location . hash === "" ) return ;
442413 if ( location . hash . replace ( "#" , "" ) === _hash ) return ;
@@ -540,46 +511,26 @@ export function HassProvider({ children, hassUrl, hassToken, locale, portalRoot,
540511 [ connection , ready ] ,
541512 ) ;
542513
543- useEffect ( ( ) => {
544- if ( connection && entityUnsubscribe . current === null ) {
545- entityUnsubscribe . current = subscribeEntities ( connection , ( $entities ) => {
546- setEntities ( $entities ) ;
547- } ) ;
548- }
549- } , [ connection , setEntities ] ) ;
550-
551514 useEffect ( ( ) => {
552515 return ( ) => {
553- authenticated . current = false ;
554- if ( entityUnsubscribe . current ) {
555- entityUnsubscribe . current ( ) ;
556- entityUnsubscribe . current = null ;
557- }
516+ reset ( ) ;
558517 } ;
559- } , [ ] ) ;
518+ } , [ reset ] ) ;
560519
561520 const debounceConnect = useDebouncedCallback (
562521 async ( ) => {
563522 try {
564- if ( _connectionRef . current && ! connection ) {
565- setConnection ( _connectionRef . current ) ;
566- authenticated . current = true ;
567- return ;
568- }
569- if ( ! _connectionRef . current && connection ) {
570- _connectionRef . current = connection ;
571- authenticated . current = true ;
572- return ;
523+ if ( authenticated . current ) {
524+ reset ( ) ;
573525 }
574- if ( authenticated . current ) return ;
575526 authenticated . current = true ;
576527 await handleConnect ( ) ;
577528 } catch ( e ) {
578529 const message = handleError ( e ) ;
579530 setError ( `Unable to connect to Home Assistant, please check the URL: "${ message } "` ) ;
580531 }
581532 } ,
582- 100 ,
533+ 25 ,
583534 {
584535 leading : true ,
585536 trailing : false ,
0 commit comments