From 149d3a585ebd5240624af4fe7488aa302918c3d1 Mon Sep 17 00:00:00 2001 From: jango-blockchained Date: Mon, 3 Feb 2025 15:11:46 +0100 Subject: [PATCH] Refactor SSE and Home Assistant Integration with Enhanced Configuration - Simplified SSE statistics retrieval in routes - Extended Home Assistant configuration with additional connection parameters - Implemented comprehensive Home Assistant instance methods for services, states, and events - Cleaned up and type-refined Home Assistant interfaces - Improved WebSocket event handling and type conversion --- src/api/routes.ts | 7 +-- src/config/index.ts | 4 +- src/hass/index.ts | 98 ++++++++++++++++++++++++++++++++++++----- src/interfaces/hass.ts | 5 +-- src/interfaces/index.ts | 2 +- 5 files changed, 94 insertions(+), 22 deletions(-) diff --git a/src/api/routes.ts b/src/api/routes.ts index 412811f..0e09d5f 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -173,12 +173,7 @@ router.get('/subscribe_events', middleware.wsRateLimiter, (req, res) => { // SSE stats endpoint router.get('/get_sse_stats', middleware.authenticate, (_req, res) => { - const stats = { - clients: sseManager.getClientCount(), - events: sseManager.getEventSubscriptions(), - entities: sseManager.getEntitySubscriptions(), - domains: sseManager.getDomainSubscriptions() - }; + const stats = sseManager.getStatistics(); res.json(stats); }); diff --git a/src/config/index.ts b/src/config/index.ts index 10e26bf..404b143 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -15,7 +15,9 @@ config({ path: resolve(process.cwd(), envFile) }); export const HASS_CONFIG = { HOST: process.env.HASS_HOST || 'http://homeassistant.local:8123', TOKEN: process.env.HASS_TOKEN, - SOCKET_URL: process.env.HASS_SOCKET_URL || 'ws://homeassistant.local:8123/api/websocket' + SOCKET_URL: process.env.HASS_SOCKET_URL || 'ws://homeassistant.local:8123/api/websocket', + BASE_URL: process.env.HASS_HOST || 'http://homeassistant.local:8123', + SOCKET_TOKEN: process.env.HASS_TOKEN }; // Server Configuration diff --git a/src/hass/index.ts b/src/hass/index.ts index 29e9718..946769d 100644 --- a/src/hass/index.ts +++ b/src/hass/index.ts @@ -286,7 +286,79 @@ export class HassInstanceImpl implements HassInstance { public readonly token: string; public wsClient: HassWebSocketClient | undefined; - public services!: HassServices; + public readonly services: HassInstance['services']; + public readonly states: HassInstance['states']; + public readonly connection: HassInstance['connection']; + + constructor(baseUrl: string, token: string) { + this.baseUrl = baseUrl; + this.token = token; + + // Initialize services + this.services = { + get: async () => { + const response = await fetch(`${this.baseUrl}/api/services`, { + headers: { + Authorization: `Bearer ${this.token}`, + 'Content-Type': 'application/json', + }, + }); + if (!response.ok) { + throw new Error(`Failed to fetch services: ${response.statusText}`); + } + return response.json(); + }, + call: async (domain: string, service: string, serviceData?: Record) => { + const response = await fetch(`${this.baseUrl}/api/services/${domain}/${service}`, { + method: 'POST', + headers: { + Authorization: `Bearer ${this.token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(serviceData), + }); + if (!response.ok) { + throw new Error(`Service call failed: ${response.statusText}`); + } + } + }; + + // Initialize states + this.states = { + get: async () => { + const response = await fetch(`${this.baseUrl}/api/states`, { + headers: { + Authorization: `Bearer ${this.token}`, + 'Content-Type': 'application/json', + }, + }); + if (!response.ok) { + throw new Error(`Failed to fetch states: ${response.statusText}`); + } + return response.json(); + }, + subscribe: async (callback: (states: HassEntity[]) => void) => { + return this.subscribeEvents((event: HassEvent) => { + if (event.event_type === 'state_changed') { + this.states.get().then(callback); + } + }, 'state_changed'); + }, + unsubscribe: (subscription: number) => { + this.unsubscribeEvents(subscription); + } + }; + + // Initialize connection + this.connection = { + socket: new WebSocket(this.baseUrl.replace(/^http/, 'ws') + '/api/websocket'), + subscribeEvents: this.subscribeEvents.bind(this), + unsubscribeEvents: this.unsubscribeEvents.bind(this) + }; + + this.initialize(); + } + public als!: AlsExtension; public context!: TContext; public event!: EventEmitter<[never]>; @@ -315,15 +387,8 @@ export class HassInstanceImpl implements HassInstance { zone: typeof Zone; }>; - constructor(baseUrl: string, token: string) { - this.baseUrl = baseUrl; - this.token = token; - this.initialize(); - } - private initialize() { // Initialize all required properties with proper type instantiation - this.services = {} as HassServices; this.als = {} as AlsExtension; this.context = {} as TContext; this.event = new EventEmitter(); @@ -383,7 +448,7 @@ export class HassInstanceImpl implements HassInstance { } } - async subscribeEvents(callback: (event: HomeAssistant.Event) => void, eventType?: string): Promise { + async subscribeEvents(callback: (event: HassEvent) => void, eventType?: string): Promise { if (!this.wsClient) { this.wsClient = new HassWebSocketClient( this.baseUrl.replace(/^http/, 'ws') + '/api/websocket', @@ -392,7 +457,20 @@ export class HassInstanceImpl implements HassInstance { await this.wsClient.connect(); } - return this.wsClient.subscribeEvents(callback, eventType); + return this.wsClient.subscribeEvents((data: any) => { + const hassEvent: HassEvent = { + event_type: data.event_type, + data: data.data, + origin: data.origin, + time_fired: data.time_fired, + context: { + id: data.context.id, + parent_id: data.context.parent_id, + user_id: data.context.user_id + } + }; + callback(hassEvent); + }, eventType); } async unsubscribeEvents(subscriptionId: number): Promise { diff --git a/src/interfaces/hass.ts b/src/interfaces/hass.ts index 382ec02..c566b13 100644 --- a/src/interfaces/hass.ts +++ b/src/interfaces/hass.ts @@ -77,7 +77,4 @@ export interface HassEvent { parent_id?: string; user_id?: string; }; -} - -// Re-export entity types from index.ts -export { HassEntity, HassState } from './index.js'; \ No newline at end of file +} \ No newline at end of file diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 49ad9eb..a0273f6 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -181,5 +181,5 @@ export interface AutomationConfigParams { }; } -// Re-export Home Assistant types +// Re-export all Home Assistant types export * from './hass.js'; \ No newline at end of file