ha-javascript-proxy
v1.0.0-rc.1
Published
A typed, reactive wrapper around home-assistant-js-websocket
Maintainers
Readme
ha-javascript-proxy
A typed, reactive wrapper around home-assistant-js-websocket — because life's too short to guess entity attribute names.
Install
npm install ha-javascript-proxy home-assistant-js-websocket[!WARNING] Node.js 18 or later is required.
Quick Start
import { createConnection } from 'ha-javascript-proxy';
const conn = createConnection({
host: 'homeassistant.local',
token: 'YOUR_LONG_LIVED_ACCESS_TOKEN',
});
await conn.ready();
const light = await conn.getEntity('light.bedroom');
if (light) {
console.log(light.state); // 'on' | 'off'
console.log(light.attributes.brightness); // number | null | undefined
await light.toggle();
}[!TIP] Generate a long-lived access token in your HA profile under Security → Long-lived access tokens.
API Reference
createConnection(options)
Returns a Connection object synchronously. Call conn.ready() before accessing entities, services, or config.
const conn = createConnection({ host: 'homeassistant.local', token: '...' });
await conn.ready();Options:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| host | string | ✓ | Hostname or full URL (http://... or https://...) |
| token | string | ✓ | Long-lived access token |
| port | number | — | Port override (default: 80/443 based on protocol) |
Connection
conn.ready(): Promise<void>
Resolves when the WebSocket handshake and HA config fetch complete. Must be awaited before using entities or services.
conn.isReady: boolean
Synchronous readiness check. true after conn.ready() resolves.
conn.config: HAConfig
The current HA configuration. Throws if called before conn.ready().
console.log(conn.config.version); // '2024.3.0'
console.log(conn.config.locationName); // 'Home'
console.log(conn.config.timezone); // 'Europe/Paris'conn.socket
EventEmitter for raw connection lifecycle events.
conn.socket.on('ready', () => console.log('Connected'));
conn.socket.on('disconnected', () => console.log('Connection lost'));
conn.socket.on('reconnect-error', (code) => console.log('Reconnect failed:', code));conn.homeassistant
EventEmitter + service proxy for the homeassistant domain.
conn.homeassistant.on('change', (state) => console.log('HA state:', state));
await conn.homeassistant.restart();
await conn.homeassistant.checkConfig();Entities
conn.getEntity(entityId): Promise<EntityProxy | undefined>
Returns a typed, reactive entity proxy. Returns undefined if the entity does not exist.
All native HA domains are automatically covered — entity.state and entity.attributes are typed for each domain. Unrecognised domains fall back to entity.state: string.
const light = await conn.getEntity('light.bedroom');
if (light) {
console.log(light.entityId); // 'light.bedroom'
console.log(light.state); // 'on' | 'off' | 'unavailable' | 'unknown'
console.log(light.lastChanged); // Date
console.log(light.lastUpdated); // Date
console.log(light.attributes.brightness); // number | null | undefined
}Common attributes (available on every entity regardless of domain):
| Attribute | Type | Description |
|-----------|------|-------------|
| friendlyName | string \| undefined | Display name from HA |
| icon | string \| undefined | MDI icon (mdi:lightbulb) |
| unitOfMeasurement | string \| undefined | Unit (e.g. °C, %) |
| deviceClass | string \| undefined | Device class (e.g. motion, temperature) |
| entityPicture | string \| undefined | Entity picture URL |
Entity events:
light.on('change', (newState, oldState) => {
console.log(`Light changed from ${oldState} to ${newState}`);
});
light.on('update', () => {
// Fires on any state or attribute change
});Calling services:
// camelCase proxy — snake_case HA actions mapped automatically:
await light.turnOn({ brightness: 128 });
await light.toggle();
// Explicit callService — action without domain prefix:
await light.callService('turn_on', { brightness: 128 });Feature support:
entity.supportsFeature(feature) tests whether the entity's supported_features bitmask includes a given capability.
import { LightFeatures } from 'ha-javascript-proxy';
if (light.supportsFeature(LightFeatures.EFFECT)) {
await light.setEffect('rainbow');
}Feature constant objects are exported per domain: LightFeatures, CoverFeatures, FanFeatures, and more. Entities whose domain defines no Feature constants return true from supportsFeature() (permissive fallback).
State constants:
import { State } from 'ha-javascript-proxy';
if (light.state === State.ON) { /* ... */ }
if (sensor.state === State.UNAVAILABLE) { /* ... */ }State mirrors homeassistant/const.py — use State.ON, State.OFF, State.UNAVAILABLE, State.UNKNOWN, State.PLAYING, etc.
Services
conn.callService(name, params?, target?): Promise<unknown>
Calls any HA service directly. Service name format: 'domain.service'.
await conn.callService('light.turn_on', { brightness_pct: 50 }, { entity_id: 'light.bedroom' });
await conn.callService('homeassistant.restart');Registry
conn.registry
Access HA registry data. All methods return Promise and require conn.ready().
| Method | Returns |
|--------|---------|
| conn.registry.getAreas() | Promise<AreaRegistryEntry[]> |
| conn.registry.getDevices() | Promise<DeviceRegistryEntry[]> |
| conn.registry.getEntityRegistry() | Promise<EntityRegistryEntry[]> |
| conn.registry.getFloorRegistry() | Promise<FloorRegistryEntry[]> |
| conn.registry.getLabelRegistry() | Promise<LabelRegistryEntry[]> |
const areas = await conn.registry.getAreas();
// [{ areaId: 'living_room', name: 'Living Room', aliases: [], ... }][!NOTE]
getFloorRegistry()andgetLabelRegistry()require HA 2024.4+. They return[]on older instances.
Events Reference
Entity events
const light = await conn.getEntity('light.bedroom');
light?.on('change', (newState, oldState) => {
console.log(`State: ${oldState} → ${newState}`);
});
light?.on('update', () => {
// Fires on any change — state or attributes
console.log('Attributes updated:', light.attributes);
});Socket events
conn.socket.on('ready', () => {
console.log('WebSocket connected');
});
conn.socket.on('disconnected', () => {
console.log('WebSocket disconnected');
});
conn.socket.on('reconnect-error', (code) => {
console.log('Reconnect failed with code:', code);
});Homeassistant events
conn.homeassistant.on('change', (state, oldState) => {
console.log(`HA state: ${oldState} → ${state}`);
// state: 'RUNNING' | 'NOT_RUNNING' | 'STARTING' | 'STOPPING' | 'FINAL_WRITE'
});
conn.homeassistant.on('update', () => {
// Fires on any config update (component loaded, core_config_updated)
});License
MIT
