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
This commit is contained in:
@@ -173,12 +173,7 @@ router.get('/subscribe_events', middleware.wsRateLimiter, (req, res) => {
|
|||||||
|
|
||||||
// SSE stats endpoint
|
// SSE stats endpoint
|
||||||
router.get('/get_sse_stats', middleware.authenticate, (_req, res) => {
|
router.get('/get_sse_stats', middleware.authenticate, (_req, res) => {
|
||||||
const stats = {
|
const stats = sseManager.getStatistics();
|
||||||
clients: sseManager.getClientCount(),
|
|
||||||
events: sseManager.getEventSubscriptions(),
|
|
||||||
entities: sseManager.getEntitySubscriptions(),
|
|
||||||
domains: sseManager.getDomainSubscriptions()
|
|
||||||
};
|
|
||||||
res.json(stats);
|
res.json(stats);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ config({ path: resolve(process.cwd(), envFile) });
|
|||||||
export const HASS_CONFIG = {
|
export const HASS_CONFIG = {
|
||||||
HOST: process.env.HASS_HOST || 'http://homeassistant.local:8123',
|
HOST: process.env.HASS_HOST || 'http://homeassistant.local:8123',
|
||||||
TOKEN: process.env.HASS_TOKEN,
|
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
|
// Server Configuration
|
||||||
|
|||||||
@@ -286,7 +286,79 @@ export class HassInstanceImpl implements HassInstance {
|
|||||||
public readonly token: string;
|
public readonly token: string;
|
||||||
public wsClient: HassWebSocketClient | undefined;
|
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<string, any>) => {
|
||||||
|
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 als!: AlsExtension;
|
||||||
public context!: TContext;
|
public context!: TContext;
|
||||||
public event!: EventEmitter<[never]>;
|
public event!: EventEmitter<[never]>;
|
||||||
@@ -315,15 +387,8 @@ export class HassInstanceImpl implements HassInstance {
|
|||||||
zone: typeof Zone;
|
zone: typeof Zone;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
constructor(baseUrl: string, token: string) {
|
|
||||||
this.baseUrl = baseUrl;
|
|
||||||
this.token = token;
|
|
||||||
this.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initialize() {
|
private initialize() {
|
||||||
// Initialize all required properties with proper type instantiation
|
// Initialize all required properties with proper type instantiation
|
||||||
this.services = {} as HassServices;
|
|
||||||
this.als = {} as AlsExtension;
|
this.als = {} as AlsExtension;
|
||||||
this.context = {} as TContext;
|
this.context = {} as TContext;
|
||||||
this.event = new EventEmitter();
|
this.event = new EventEmitter();
|
||||||
@@ -383,7 +448,7 @@ export class HassInstanceImpl implements HassInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async subscribeEvents(callback: (event: HomeAssistant.Event) => void, eventType?: string): Promise<number> {
|
async subscribeEvents(callback: (event: HassEvent) => void, eventType?: string): Promise<number> {
|
||||||
if (!this.wsClient) {
|
if (!this.wsClient) {
|
||||||
this.wsClient = new HassWebSocketClient(
|
this.wsClient = new HassWebSocketClient(
|
||||||
this.baseUrl.replace(/^http/, 'ws') + '/api/websocket',
|
this.baseUrl.replace(/^http/, 'ws') + '/api/websocket',
|
||||||
@@ -392,7 +457,20 @@ export class HassInstanceImpl implements HassInstance {
|
|||||||
await this.wsClient.connect();
|
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<void> {
|
async unsubscribeEvents(subscriptionId: number): Promise<void> {
|
||||||
|
|||||||
@@ -78,6 +78,3 @@ export interface HassEvent {
|
|||||||
user_id?: string;
|
user_id?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-export entity types from index.ts
|
|
||||||
export { HassEntity, HassState } from './index.js';
|
|
||||||
@@ -181,5 +181,5 @@ export interface AutomationConfigParams {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-export Home Assistant types
|
// Re-export all Home Assistant types
|
||||||
export * from './hass.js';
|
export * from './hass.js';
|
||||||
Reference in New Issue
Block a user