/** * API Client for communicating with Onebox daemon * * Provides methods for CLI commands to interact with running daemon via HTTP API */ import type { IService, IRegistry, IDnsRecord, ISslCertificate, IServiceDeployOptions, } from '../types.ts'; import { getErrorMessage } from '../utils/error.ts'; export class OneboxApiClient { private baseUrl: string; private token?: string; constructor(port = 3000) { this.baseUrl = `http://localhost:${port}`; } /** * Check if daemon is reachable */ async isReachable(): Promise { try { const response = await fetch(`${this.baseUrl}/api/status`, { signal: AbortSignal.timeout(5000), // 5 second timeout }); return response.ok; } catch { return false; } } // ============ Service Operations ============ async deployService(config: IServiceDeployOptions): Promise { return await this.request('POST', '/api/services', config); } async removeService(name: string): Promise { await this.request('DELETE', `/api/services/${name}`); } async startService(name: string): Promise { await this.request('POST', `/api/services/${name}/start`); } async stopService(name: string): Promise { await this.request('POST', `/api/services/${name}/stop`); } async restartService(name: string): Promise { await this.request('POST', `/api/services/${name}/restart`); } async listServices(): Promise { return await this.request('GET', '/api/services'); } async getServiceLogs(name: string, limit = 1000): Promise { const result = await this.request<{ logs: string[] }>( 'GET', `/api/services/${name}/logs?limit=${limit}` ); return result.logs; } // ============ Registry Operations ============ async addRegistry(url: string, username: string, password: string): Promise { await this.request('POST', '/api/registries', { url, username, password }); } async removeRegistry(url: string): Promise { await this.request('DELETE', `/api/registries/${encodeURIComponent(url)}`); } async listRegistries(): Promise { return await this.request('GET', '/api/registries'); } // ============ DNS Operations ============ async addDnsRecord(domain: string): Promise { await this.request('POST', '/api/dns', { domain }); } async removeDnsRecord(domain: string): Promise { await this.request('DELETE', `/api/dns/${domain}`); } async listDnsRecords(): Promise { return await this.request('GET', '/api/dns'); } async syncDns(): Promise { await this.request('POST', '/api/dns/sync'); } // ============ SSL Operations ============ async renewCertificate(domain?: string): Promise { const path = domain ? `/api/ssl/renew/${domain}` : '/api/ssl/renew'; await this.request('POST', path); } async listCertificates(): Promise { return await this.request('GET', '/api/ssl'); } async forceRenewCertificate(domain: string): Promise { await this.request('POST', `/api/ssl/renew/${domain}?force=true`); } // ============ Nginx Operations ============ async reloadNginx(): Promise { await this.request('POST', '/api/nginx/reload'); } async testNginx(): Promise<{ success: boolean; output: string }> { return await this.request('POST', '/api/nginx/test'); } async getNginxStatus(): Promise<{ status: string }> { return await this.request('GET', '/api/nginx/status'); } // ============ Config Operations ============ async getSettings(): Promise> { return await this.request>('GET', '/api/config'); } async setSetting(key: string, value: string): Promise { await this.request('POST', '/api/config', { key, value }); } // ============ System Operations ============ async getStatus(): Promise<{ services: { total: number; running: number; stopped: number }; uptime: number; }> { return await this.request('GET', '/api/status'); } // ============ Helper Methods ============ /** * Make HTTP request to daemon */ private async request( method: string, path: string, body?: unknown ): Promise { const url = `${this.baseUrl}${path}`; const headers: Record = { 'Content-Type': 'application/json', }; if (this.token) { headers['Authorization'] = `Bearer ${this.token}`; } const options: RequestInit = { method, headers, signal: AbortSignal.timeout(30000), // 30 second timeout }; if (body) { options.body = JSON.stringify(body); } try { const response = await fetch(url, options); if (!response.ok) { const errorData = await response.json().catch(() => ({ message: response.statusText })); throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`); } // For DELETE and some POST requests, there might be no content if (response.status === 204 || response.headers.get('content-length') === '0') { return undefined as T; } return await response.json(); } catch (error) { if (error instanceof Error && error.name === 'TimeoutError') { throw new Error('Request timed out. Daemon might be unresponsive.'); } throw error; } } /** * Set authentication token */ setToken(token: string): void { this.token = token; } }