136 lines
3.5 KiB
TypeScript
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;
|
|
}
|
|
}
|