/**
* HTTP client for the Viewpo bridge API.
* Communicates with the Viewpo macOS app's NWListener on localhost.
*/
import type {
ScreenshotRequest,
ScreenshotResponse,
LayoutMapRequest,
LayoutMapResponse,
CompareRequest,
CompareResponse,
HealthResponse,
ErrorResponse,
} from "./types.js";
export class ViewpoBridgeClient {
private readonly baseURL: string;
private readonly authToken: string;
constructor(port: number = 9847, authToken: string = "") {
this.baseURL = `http://127.0.0.1:${port}`;
this.authToken = authToken;
}
async health(): Promise<HealthResponse> {
return this.get<HealthResponse>("/health");
}
async screenshot(request: ScreenshotRequest): Promise<ScreenshotResponse> {
return this.post<ScreenshotResponse>("/screenshot", request);
}
async layoutMap(request: LayoutMapRequest): Promise<LayoutMapResponse> {
return this.post<LayoutMapResponse>("/layout", request);
}
async compare(request: CompareRequest): Promise<CompareResponse> {
return this.post<CompareResponse>("/compare", request);
}
private async get<T>(path: string): Promise<T> {
const response = await fetch(`${this.baseURL}${path}`, {
method: "GET",
headers: this.headers(false),
});
return this.handleResponse<T>(response);
}
private async post<T>(path: string, body: unknown): Promise<T> {
const response = await fetch(`${this.baseURL}${path}`, {
method: "POST",
headers: this.headers(true),
body: JSON.stringify(body),
});
return this.handleResponse<T>(response);
}
private headers(includeContentType: boolean): Record<string, string> {
const h: Record<string, string> = {};
if (this.authToken) {
h["Authorization"] = `Bearer ${this.authToken}`;
}
if (includeContentType) {
h["Content-Type"] = "application/json";
}
return h;
}
private async handleResponse<T>(response: Response): Promise<T> {
const text = await response.text();
if (!response.ok) {
let message = `HTTP ${response.status}`;
try {
const err = JSON.parse(text) as ErrorResponse;
message = err.error || message;
} catch {
if (text) message = text;
}
throw new Error(
`Viewpo bridge error (${response.status}): ${message}`
);
}
try {
return JSON.parse(text) as T;
} catch {
throw new Error(`Invalid JSON response from Viewpo bridge: ${text.slice(0, 200)}`);
}
}
}