refactor: migrate to Elysia and enhance security middleware
- Replaced Express with Elysia for improved performance and type safety - Integrated Elysia middleware for rate limiting, security headers, and request validation - Refactored security utilities to work with Elysia's context and request handling - Updated token management and validation logic - Added comprehensive security headers and input sanitization - Simplified server initialization and error handling - Updated documentation with new setup and configuration details
This commit is contained in:
@@ -1,34 +1,90 @@
|
||||
import { CreateApplication } from "@digital-alchemy/core";
|
||||
import { LIB_HASS } from "@digital-alchemy/hass";
|
||||
import type { HassEntity } from "../interfaces/hass.js";
|
||||
|
||||
// Create the application following the documentation example
|
||||
const app = CreateApplication({
|
||||
libraries: [LIB_HASS],
|
||||
name: "home_automation",
|
||||
configuration: {
|
||||
hass: {
|
||||
BASE_URL: {
|
||||
type: "string" as const,
|
||||
default: process.env.HASS_HOST || "http://localhost:8123",
|
||||
description: "Home Assistant URL",
|
||||
},
|
||||
TOKEN: {
|
||||
type: "string" as const,
|
||||
default: process.env.HASS_TOKEN || "",
|
||||
description: "Home Assistant long-lived access token",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
class HomeAssistantAPI {
|
||||
private baseUrl: string;
|
||||
private token: string;
|
||||
|
||||
let instance: Awaited<ReturnType<typeof app.bootstrap>>;
|
||||
constructor() {
|
||||
this.baseUrl = process.env.HASS_HOST || "http://localhost:8123";
|
||||
this.token = process.env.HASS_TOKEN || "";
|
||||
|
||||
if (!this.token || this.token === "your_hass_token_here") {
|
||||
throw new Error("HASS_TOKEN is required but not set in environment variables");
|
||||
}
|
||||
|
||||
console.log(`Initializing Home Assistant API with base URL: ${this.baseUrl}`);
|
||||
}
|
||||
|
||||
private async fetchApi(endpoint: string, options: RequestInit = {}) {
|
||||
const url = `${this.baseUrl}/api/${endpoint}`;
|
||||
console.log(`Making request to: ${url}`);
|
||||
console.log('Request options:', {
|
||||
method: options.method || 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer [REDACTED]',
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
body: options.body ? JSON.parse(options.body as string) : undefined
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Home Assistant API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText
|
||||
});
|
||||
throw new Error(`Home Assistant API error: ${response.status} ${response.statusText} - ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Response data:', data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Failed to make request:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getStates(): Promise<HassEntity[]> {
|
||||
return this.fetchApi("states");
|
||||
}
|
||||
|
||||
async getState(entityId: string): Promise<HassEntity> {
|
||||
return this.fetchApi(`states/${entityId}`);
|
||||
}
|
||||
|
||||
async callService(domain: string, service: string, data: Record<string, any>): Promise<void> {
|
||||
await this.fetchApi(`services/${domain}/${service}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let instance: HomeAssistantAPI | null = null;
|
||||
|
||||
export async function get_hass() {
|
||||
if (!instance) {
|
||||
try {
|
||||
instance = await app.bootstrap();
|
||||
instance = new HomeAssistantAPI();
|
||||
// Verify connection by trying to get states
|
||||
await instance.getStates();
|
||||
console.log('Successfully connected to Home Assistant');
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize Home Assistant:", error);
|
||||
console.error('Failed to initialize Home Assistant connection:', error);
|
||||
instance = null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -42,23 +98,28 @@ export async function call_service(
|
||||
data: Record<string, any>,
|
||||
) {
|
||||
const hass = await get_hass();
|
||||
return hass.hass.internals.callService(domain, service, data);
|
||||
return hass.callService(domain, service, data);
|
||||
}
|
||||
|
||||
// Helper function to list devices
|
||||
export async function list_devices() {
|
||||
const hass = await get_hass();
|
||||
return hass.hass.device.list();
|
||||
const states = await hass.getStates();
|
||||
return states.map((state: HassEntity) => ({
|
||||
entity_id: state.entity_id,
|
||||
state: state.state,
|
||||
attributes: state.attributes
|
||||
}));
|
||||
}
|
||||
|
||||
// Helper function to get entity states
|
||||
export async function get_states() {
|
||||
const hass = await get_hass();
|
||||
return hass.hass.internals.getStates();
|
||||
return hass.getStates();
|
||||
}
|
||||
|
||||
// Helper function to get a specific entity state
|
||||
export async function get_state(entity_id: string) {
|
||||
const hass = await get_hass();
|
||||
return hass.hass.internals.getState(entity_id);
|
||||
return hass.getState(entity_id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user