import * as plugins from './plugins.js'; import type { ISocketClientOptions, IApiResponse } from './interfaces/index.js'; import { SmartVMError } from './interfaces/index.js'; /** * HTTP client that communicates with Firecracker over a Unix domain socket. * Uses @push.rocks/smartrequest with the `http://unix::` URL format. */ export class SocketClient { private socketPath: string; constructor(options: ISocketClientOptions) { this.socketPath = options.socketPath; } /** * Build the Unix socket URL for a given API path. */ private buildUrl(apiPath: string): string { return `http://unix:${this.socketPath}:${apiPath}`; } /** * Perform a GET request. */ public async get(apiPath: string): Promise> { const url = this.buildUrl(apiPath); try { const response = await plugins.SmartRequest.create() .url(url) .get(); const statusCode = response.status; let body: T; try { body = await response.json() as T; } catch { body = undefined as any; } return { statusCode, body, ok: response.ok, }; } catch (err) { throw new SmartVMError( `GET ${apiPath} failed: ${(err as Error).message}`, 'SOCKET_REQUEST_FAILED', ); } } /** * Perform a PUT request with a JSON body. */ public async put(apiPath: string, body?: Record): Promise> { const url = this.buildUrl(apiPath); try { let request = plugins.SmartRequest.create().url(url); if (body !== undefined) { request = request.json(body); } const response = await request.put(); const statusCode = response.status; let responseBody: T; try { responseBody = await response.json() as T; } catch { responseBody = undefined as any; } return { statusCode, body: responseBody, ok: response.ok, }; } catch (err) { throw new SmartVMError( `PUT ${apiPath} failed: ${(err as Error).message}`, 'SOCKET_REQUEST_FAILED', ); } } /** * Perform a PATCH request with a JSON body. */ public async patch(apiPath: string, body?: Record): Promise> { const url = this.buildUrl(apiPath); try { let request = plugins.SmartRequest.create().url(url); if (body !== undefined) { request = request.json(body); } const response = await request.patch(); const statusCode = response.status; let responseBody: T; try { responseBody = await response.json() as T; } catch { responseBody = undefined as any; } return { statusCode, body: responseBody, ok: response.ok, }; } catch (err) { throw new SmartVMError( `PATCH ${apiPath} failed: ${(err as Error).message}`, 'SOCKET_REQUEST_FAILED', ); } } /** * Check if the Firecracker API socket is ready by polling GET /. */ public async isReady(timeoutMs: number = 5000): Promise { const start = Date.now(); while (Date.now() - start < timeoutMs) { try { const response = await this.get('/'); if (response.ok || response.statusCode === 200 || response.statusCode === 400) { return true; } } catch { // Socket not ready yet } await plugins.smartdelay.delayFor(100); } return false; } }