Files
smartvm/ts/classes.socketclient.ts
2026-02-08 21:47:33 +00:00

136 lines
3.5 KiB
TypeScript

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:<socket>:<path>` 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<T = any>(apiPath: string): Promise<IApiResponse<T>> {
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<T = any>(apiPath: string, body?: Record<string, any>): Promise<IApiResponse<T>> {
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<T = any>(apiPath: string, body?: Record<string, any>): Promise<IApiResponse<T>> {
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<boolean> {
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;
}
}