192 lines
5.8 KiB
TypeScript
192 lines
5.8 KiB
TypeScript
|
|
import { createApiClient, type ApiClient } from "@geo/http-client";
|
||
|
|
import type {
|
||
|
|
CompleteDesktopTaskRequest,
|
||
|
|
DesktopArticleContent,
|
||
|
|
DesktopAccountInfo,
|
||
|
|
DesktopClientHeartbeatRequest,
|
||
|
|
DesktopClientHeartbeatResponse,
|
||
|
|
DesktopTaskInfo,
|
||
|
|
DesktopRuntimeSessionSyncRequest,
|
||
|
|
ExtendDesktopTaskRequest,
|
||
|
|
LeaseDesktopTaskRequest,
|
||
|
|
LeaseDesktopTaskResponse,
|
||
|
|
ParkDesktopTaskRequest,
|
||
|
|
UpsertDesktopAccountRequest,
|
||
|
|
} from "@geo/shared-types";
|
||
|
|
|
||
|
|
type TransportAuthState = "pending" | "registered" | "expired";
|
||
|
|
type TransportSseState = "idle" | "connecting" | "streaming";
|
||
|
|
|
||
|
|
interface DesktopTransportSession {
|
||
|
|
baseURL: string;
|
||
|
|
clientToken: string | null;
|
||
|
|
}
|
||
|
|
|
||
|
|
let desktopApiClient: ApiClient | null = null;
|
||
|
|
let transportSession: DesktopTransportSession = {
|
||
|
|
baseURL: process.env.DESKTOP_API_BASE_URL ?? "http://localhost:8080",
|
||
|
|
clientToken: null,
|
||
|
|
};
|
||
|
|
|
||
|
|
const transportState = {
|
||
|
|
baseURL: transportSession.baseURL,
|
||
|
|
initializedAt: 0,
|
||
|
|
mode: "rabbitmq-first" as const,
|
||
|
|
auth: "pending" as TransportAuthState,
|
||
|
|
sseState: "idle" as TransportSseState,
|
||
|
|
lastHeartbeatAt: 0,
|
||
|
|
lastHeartbeatStatus: "idle" as "idle" | "success" | "failed",
|
||
|
|
lastPullAt: 0,
|
||
|
|
lastPullStatus: "idle" as "idle" | "success" | "failed",
|
||
|
|
};
|
||
|
|
|
||
|
|
function createDesktopClient(baseURL: string): ApiClient {
|
||
|
|
const client = createApiClient({
|
||
|
|
baseURL,
|
||
|
|
timeoutMs: 15000,
|
||
|
|
});
|
||
|
|
|
||
|
|
client.raw.interceptors.request.use((config) => {
|
||
|
|
if (transportSession.clientToken) {
|
||
|
|
config.headers = config.headers ?? {};
|
||
|
|
(config.headers as Record<string, string>).Authorization = `Bearer ${transportSession.clientToken}`;
|
||
|
|
}
|
||
|
|
return config;
|
||
|
|
});
|
||
|
|
|
||
|
|
return client;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function initTransport(): ApiClient {
|
||
|
|
if (desktopApiClient) {
|
||
|
|
return desktopApiClient;
|
||
|
|
}
|
||
|
|
|
||
|
|
transportState.initializedAt = transportState.initializedAt || Date.now();
|
||
|
|
transportState.baseURL = transportSession.baseURL;
|
||
|
|
desktopApiClient = createDesktopClient(transportSession.baseURL);
|
||
|
|
return desktopApiClient;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function configureTransport(session: DesktopRuntimeSessionSyncRequest | null): ApiClient | null {
|
||
|
|
if (!session) {
|
||
|
|
transportSession = {
|
||
|
|
baseURL: process.env.DESKTOP_API_BASE_URL ?? "http://localhost:8080",
|
||
|
|
clientToken: null,
|
||
|
|
};
|
||
|
|
transportState.baseURL = transportSession.baseURL;
|
||
|
|
transportState.auth = "pending";
|
||
|
|
desktopApiClient = null;
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
transportSession = {
|
||
|
|
baseURL: session.api_base_url.trim() || "http://localhost:8080",
|
||
|
|
clientToken: session.client_token,
|
||
|
|
};
|
||
|
|
transportState.baseURL = transportSession.baseURL;
|
||
|
|
transportState.auth = session.client_token ? "registered" : "pending";
|
||
|
|
transportState.initializedAt = Date.now();
|
||
|
|
desktopApiClient = createDesktopClient(transportSession.baseURL);
|
||
|
|
return desktopApiClient;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function setTransportAuthState(next: TransportAuthState): void {
|
||
|
|
transportState.auth = next;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function setTransportSseState(next: TransportSseState): void {
|
||
|
|
transportState.sseState = next;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function noteTransportHeartbeat(success: boolean): void {
|
||
|
|
transportState.lastHeartbeatAt = Date.now();
|
||
|
|
transportState.lastHeartbeatStatus = success ? "success" : "failed";
|
||
|
|
}
|
||
|
|
|
||
|
|
export function noteTransportPull(success: boolean): void {
|
||
|
|
transportState.lastPullAt = Date.now();
|
||
|
|
transportState.lastPullStatus = success ? "success" : "failed";
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getDesktopApiClient(): ApiClient {
|
||
|
|
return desktopApiClient ?? initTransport();
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getTransportSnapshot() {
|
||
|
|
return {
|
||
|
|
...transportState,
|
||
|
|
hasClient: desktopApiClient !== null,
|
||
|
|
hasClientToken: Boolean(transportSession.clientToken),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function heartbeatDesktopClient(
|
||
|
|
payload: DesktopClientHeartbeatRequest,
|
||
|
|
): Promise<DesktopClientHeartbeatResponse> {
|
||
|
|
return getDesktopApiClient().post<DesktopClientHeartbeatResponse, DesktopClientHeartbeatRequest>(
|
||
|
|
"/api/desktop/clients/heartbeat",
|
||
|
|
payload,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function listDesktopAccounts(): Promise<DesktopAccountInfo[]> {
|
||
|
|
return getDesktopApiClient().get<DesktopAccountInfo[]>("/api/desktop/accounts");
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function getDesktopArticleContent(articleId: number): Promise<DesktopArticleContent> {
|
||
|
|
return getDesktopApiClient().get<DesktopArticleContent>(`/api/desktop/content/articles/${articleId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function upsertDesktopAccount(
|
||
|
|
payload: UpsertDesktopAccountRequest,
|
||
|
|
): Promise<DesktopAccountInfo> {
|
||
|
|
return getDesktopApiClient().post<DesktopAccountInfo, UpsertDesktopAccountRequest>(
|
||
|
|
"/api/desktop/accounts",
|
||
|
|
payload,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function leaseDesktopTask(
|
||
|
|
request: LeaseDesktopTaskRequest,
|
||
|
|
options: { fromParked?: boolean } = {},
|
||
|
|
): Promise<LeaseDesktopTaskResponse> {
|
||
|
|
const taskId = request.task_id?.trim();
|
||
|
|
const path = taskId ? `/api/desktop/tasks/${taskId}/lease` : "/api/desktop/tasks/lease";
|
||
|
|
const query = options.fromParked ? "?from_parked=true" : "";
|
||
|
|
return getDesktopApiClient().post<LeaseDesktopTaskResponse, LeaseDesktopTaskRequest>(
|
||
|
|
`${path}${query}`,
|
||
|
|
taskId ? { kind: request.kind } : request,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function extendDesktopTask(
|
||
|
|
taskId: string,
|
||
|
|
payload: ExtendDesktopTaskRequest,
|
||
|
|
): Promise<DesktopTaskInfo> {
|
||
|
|
return getDesktopApiClient().post<DesktopTaskInfo, ExtendDesktopTaskRequest>(
|
||
|
|
`/api/desktop/tasks/${taskId}/extend`,
|
||
|
|
payload,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function parkDesktopTask(
|
||
|
|
taskId: string,
|
||
|
|
payload: ParkDesktopTaskRequest,
|
||
|
|
): Promise<DesktopTaskInfo> {
|
||
|
|
return getDesktopApiClient().post<DesktopTaskInfo, ParkDesktopTaskRequest>(
|
||
|
|
`/api/desktop/tasks/${taskId}/park`,
|
||
|
|
payload,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function completeDesktopTask(
|
||
|
|
taskId: string,
|
||
|
|
payload: CompleteDesktopTaskRequest,
|
||
|
|
): Promise<DesktopTaskInfo> {
|
||
|
|
return getDesktopApiClient().post<DesktopTaskInfo, CompleteDesktopTaskRequest>(
|
||
|
|
`/api/desktop/tasks/${taskId}/result`,
|
||
|
|
payload,
|
||
|
|
);
|
||
|
|
}
|