/
klischa
/
RuView
Обзор
Документация
Войти
/
klischa
/
RuView
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
examples/frontend/src/api/client.ts
132 строки
4 KB
ruv
refactor: move frontend/ to examples/frontend/
27 май 2026, 19:20
27 май 2026, 19:20
04f205a
Код
Авторство
О чём код?
/** * HOMECORE API client. * * REST: fetch-based, bearer token auth. Base URL defaults to window.location.origin * so the Vite dev-server proxy handles the `/api` → `:8123` rewrite. * WS: native WebSocket, mirrors HA's ws handshake protocol (auth_required → auth → auth_ok). */ import type { ApiConfig, ServiceDomainView, StateView, WsAuthOk, WsAuthRequired, WsServerMessage, } from './types.js'; export interface ClientOptions { baseUrl?: string; token: string; } export class HomecoreClient { private readonly base: string; private readonly token: string; constructor(options: ClientOptions) { this.base = options.baseUrl ?? ''; this.token = options.token; } // ── REST helpers ──────────────────────────────────────────────────────────── private headers(): HeadersInit { return { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json', }; } private async get<T>(path: string): Promise<T> { const resp = await fetch(`${this.base}${path}`, { method: 'GET', headers: this.headers(), }); if (!resp.ok) { throw new Error(`GET ${path} → ${resp.status} ${resp.statusText}`); } return resp.json() as Promise<T>; } private async post<T>(path: string, body: unknown): Promise<T> { const resp = await fetch(`${this.base}${path}`, { method: 'POST', headers: this.headers(), body: JSON.stringify(body), }); if (!resp.ok) { throw new Error(`POST ${path} → ${resp.status} ${resp.statusText}`); } return resp.json() as Promise<T>; } // ── REST endpoints (mirrors rest.rs) ───────────────────────────────────── getConfig(): Promise<ApiConfig> { return this.get<ApiConfig>('/api/config'); } getStates(): Promise<StateView[]> { return this.get<StateView[]>('/api/states'); } getState(entityId: string): Promise<StateView> { return this.get<StateView>(`/api/states/${encodeURIComponent(entityId)}`); } setState(entityId: string, state: string, attributes?: Record<string, unknown>): Promise<StateView> { return this.post<StateView>(`/api/states/${encodeURIComponent(entityId)}`, { state, attributes: attributes ?? {}, }); } getServices(): Promise<ServiceDomainView[]> { return this.get<ServiceDomainView[]>('/api/services'); } callService(domain: string, service: string, data?: unknown): Promise<unknown> { return this.post<unknown>(`/api/services/${domain}/${service}`, data ?? {}); } // ── WebSocket ──────────────────────────────────────────────────────────── /** * Open an authenticated WebSocket connection. * Resolves once `auth_ok` is received; rejects on auth failure or network error. * Returns the live socket; caller is responsible for `.close()`. */ openWebSocket(wsBase?: string): Promise<WebSocket> { const resolved = wsBase ?? this.base.replace(/^http/, 'ws'); const origin = resolved || window.location.origin.replace(/^http/, 'ws'); const url = `${origin}/api/websocket`; return new Promise((resolve, reject) => { const ws = new WebSocket(url); ws.onmessage = (evt: MessageEvent<string>) => { const msg = JSON.parse(evt.data) as WsServerMessage; if ((msg as WsAuthRequired).type === 'auth_required') { ws.send(JSON.stringify({ type: 'auth', access_token: this.token })); return; } if ((msg as WsAuthOk).type === 'auth_ok') { ws.onmessage = null; resolve(ws); return; } if (msg.type === 'auth_invalid') { ws.close(); reject(new Error(`WS auth_invalid`)); } }; ws.onerror = () => reject(new Error('WebSocket connection error')); ws.onclose = () => reject(new Error('WebSocket closed before auth_ok')); }); } }