chore(frontend): introduce prettier + eslint and prune unused code
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports) - Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier - Add root scripts: format, format:check, lint, lint:fix - Reformat 257 files across admin-web, ops-web, desktop-client, packages - Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters - Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex - Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,23 +1,21 @@
|
||||
import { ApiClientError, createApiClient, type ApiClient } from "@geo/http-client";
|
||||
import { ApiClientError, createApiClient, type ApiClient } from '@geo/http-client'
|
||||
import type {
|
||||
ApiEnvelope,
|
||||
ApiErrorEnvelope,
|
||||
CancelDesktopTaskRequest,
|
||||
CompleteDesktopTaskRequest,
|
||||
CreatePublishJobResponse,
|
||||
DesktopArticleContent,
|
||||
DesktopAccountInfo,
|
||||
DesktopArticleContent,
|
||||
DesktopClientHeartbeatRequest,
|
||||
DesktopClientHeartbeatResponse,
|
||||
DesktopPublishTaskListResponse,
|
||||
DesktopTaskInfo,
|
||||
DesktopRuntimeSessionSyncRequest,
|
||||
DesktopTaskInfo,
|
||||
ExtendDesktopTaskRequest,
|
||||
LeaseDesktopTaskRequest,
|
||||
LeaseDesktopTaskResponse,
|
||||
ListDesktopPublishTasksParams,
|
||||
ReportDesktopAccountHealthRequest,
|
||||
ReportDesktopAccountHealthResponse,
|
||||
MonitoringLeaseTasksPayload,
|
||||
MonitoringLeaseTasksResponse,
|
||||
MonitoringResumeTasksPayload,
|
||||
@@ -26,145 +24,145 @@ import type {
|
||||
MonitoringSkipTaskResponse,
|
||||
MonitoringTaskResultPayload,
|
||||
MonitoringTaskResultResponse,
|
||||
ReportDesktopAccountHealthRequest,
|
||||
ReportDesktopAccountHealthResponse,
|
||||
UpsertDesktopAccountRequest,
|
||||
} from "@geo/shared-types";
|
||||
} from '@geo/shared-types'
|
||||
|
||||
import { canUseRendererDevtoolsProxy, rendererDevtoolsFetch } from "../renderer-devtools-proxy";
|
||||
import {
|
||||
failObservedRequest,
|
||||
resolveObservedRequest,
|
||||
startObservedRequest,
|
||||
} from "../network-observer";
|
||||
} from '../network-observer'
|
||||
import { canUseRendererDevtoolsProxy, rendererDevtoolsFetch } from '../renderer-devtools-proxy'
|
||||
|
||||
type TransportAuthState = "pending" | "registered" | "expired";
|
||||
type TransportDispatchState = "idle" | "connecting" | "streaming";
|
||||
type TransportAuthState = 'pending' | 'registered' | 'expired'
|
||||
type TransportDispatchState = 'idle' | 'connecting' | 'streaming'
|
||||
|
||||
type ObservedAxiosConfig = {
|
||||
method?: string;
|
||||
url?: string;
|
||||
baseURL?: string;
|
||||
headers?: Record<string, unknown>;
|
||||
__observedRequestId?: string;
|
||||
};
|
||||
method?: string
|
||||
url?: string
|
||||
baseURL?: string
|
||||
headers?: Record<string, unknown>
|
||||
__observedRequestId?: string
|
||||
}
|
||||
|
||||
interface DesktopTransportSession {
|
||||
baseURL: string;
|
||||
clientToken: string | null;
|
||||
baseURL: string
|
||||
clientToken: string | null
|
||||
}
|
||||
|
||||
interface MonitoringCancelTaskPayload {
|
||||
lease_token: string;
|
||||
reason?: string | null;
|
||||
lease_token: string
|
||||
reason?: string | null
|
||||
}
|
||||
|
||||
interface MonitoringCancelTaskResponse {
|
||||
task_id: number;
|
||||
task_status: string;
|
||||
task_id: number
|
||||
task_status: string
|
||||
}
|
||||
|
||||
let desktopApiClient: ApiClient | null = null;
|
||||
let desktopApiClient: ApiClient | null = null
|
||||
let transportSession: DesktopTransportSession = {
|
||||
baseURL: process.env.DESKTOP_API_BASE_URL ?? "http://localhost:8080",
|
||||
baseURL: process.env.DESKTOP_API_BASE_URL ?? 'http://localhost:8080',
|
||||
clientToken: null,
|
||||
};
|
||||
const ACCOUNT_UPSERT_CACHE_TTL_MS = 5 * 60_000;
|
||||
}
|
||||
const ACCOUNT_UPSERT_CACHE_TTL_MS = 5 * 60_000
|
||||
|
||||
interface AccountUpsertCacheEntry {
|
||||
signature: string;
|
||||
expiresAt: number;
|
||||
response: DesktopAccountInfo;
|
||||
inFlight: Promise<DesktopAccountInfo> | null;
|
||||
signature: string
|
||||
expiresAt: number
|
||||
response: DesktopAccountInfo
|
||||
inFlight: Promise<DesktopAccountInfo> | null
|
||||
}
|
||||
|
||||
const accountUpsertCache = new Map<string, AccountUpsertCacheEntry>();
|
||||
const accountUpsertCache = new Map<string, AccountUpsertCacheEntry>()
|
||||
|
||||
const transportState = {
|
||||
baseURL: transportSession.baseURL,
|
||||
initializedAt: 0,
|
||||
mode: "rabbitmq-first" as const,
|
||||
requestDebugMode: "main-process" as "main-process" | "renderer-devtools",
|
||||
auth: "pending" as TransportAuthState,
|
||||
dispatchState: "idle" as TransportDispatchState,
|
||||
mode: 'rabbitmq-first' as const,
|
||||
requestDebugMode: 'main-process' as 'main-process' | 'renderer-devtools',
|
||||
auth: 'pending' as TransportAuthState,
|
||||
dispatchState: 'idle' as TransportDispatchState,
|
||||
lastHeartbeatAt: 0,
|
||||
lastHeartbeatStatus: "idle" as "idle" | "success" | "failed",
|
||||
lastHeartbeatStatus: 'idle' as 'idle' | 'success' | 'failed',
|
||||
lastPullAt: 0,
|
||||
lastPullStatus: "idle" as "idle" | "success" | "failed",
|
||||
};
|
||||
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}`;
|
||||
config.headers = config.headers ?? {}
|
||||
;(config.headers as Record<string, string>).Authorization =
|
||||
`Bearer ${transportSession.clientToken}`
|
||||
}
|
||||
|
||||
const observedConfig = config as ObservedAxiosConfig;
|
||||
const observedConfig = config as ObservedAxiosConfig
|
||||
observedConfig.__observedRequestId = startObservedRequest({
|
||||
source: "transport",
|
||||
label: "desktop.api",
|
||||
source: 'transport',
|
||||
label: 'desktop.api',
|
||||
method: config.method,
|
||||
url: resolveObservedRequestURL(config.url, config.baseURL ?? baseURL),
|
||||
});
|
||||
return config;
|
||||
});
|
||||
})
|
||||
return config
|
||||
})
|
||||
|
||||
client.raw.interceptors.response.use(
|
||||
(response) => {
|
||||
const observedConfig = response.config as ObservedAxiosConfig;
|
||||
const observedConfig = response.config as ObservedAxiosConfig
|
||||
if (observedConfig.__observedRequestId) {
|
||||
resolveObservedRequest(observedConfig.__observedRequestId, {
|
||||
phase: "response",
|
||||
phase: 'response',
|
||||
status: response.status,
|
||||
});
|
||||
})
|
||||
}
|
||||
return response;
|
||||
return response
|
||||
},
|
||||
(error: unknown) => {
|
||||
const responseError = error as {
|
||||
config?: ObservedAxiosConfig;
|
||||
response?: { status?: number };
|
||||
};
|
||||
const requestId = responseError.config?.__observedRequestId;
|
||||
config?: ObservedAxiosConfig
|
||||
response?: { status?: number }
|
||||
}
|
||||
const requestId = responseError.config?.__observedRequestId
|
||||
if (requestId) {
|
||||
if (typeof responseError.response?.status === "number") {
|
||||
if (typeof responseError.response?.status === 'number') {
|
||||
resolveObservedRequest(requestId, {
|
||||
phase: "response",
|
||||
phase: 'response',
|
||||
status: responseError.response.status,
|
||||
});
|
||||
})
|
||||
} else {
|
||||
failObservedRequest(requestId, error);
|
||||
failObservedRequest(requestId, error)
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
return Promise.reject(error)
|
||||
},
|
||||
);
|
||||
)
|
||||
|
||||
return client;
|
||||
return client
|
||||
}
|
||||
|
||||
function stableJson(value: unknown): string {
|
||||
if (Array.isArray(value)) {
|
||||
return `[${value.map((item) => stableJson(item)).join(",")}]`;
|
||||
return `[${value.map((item) => stableJson(item)).join(',')}]`
|
||||
}
|
||||
if (value && typeof value === "object") {
|
||||
if (value && typeof value === 'object') {
|
||||
return `{${Object.entries(value as Record<string, unknown>)
|
||||
.sort(([left], [right]) => left.localeCompare(right))
|
||||
.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`)
|
||||
.join(",")}}`;
|
||||
.join(',')}}`
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
|
||||
function accountUpsertCacheKey(payload: UpsertDesktopAccountRequest): string {
|
||||
return [
|
||||
payload.platform.trim(),
|
||||
payload.platform_uid.trim(),
|
||||
].join("::");
|
||||
return [payload.platform.trim(), payload.platform_uid.trim()].join('::')
|
||||
}
|
||||
|
||||
function accountUpsertSignature(payload: UpsertDesktopAccountRequest): string {
|
||||
@@ -178,29 +176,32 @@ function accountUpsertSignature(payload: UpsertDesktopAccountRequest): string {
|
||||
verified_at: payload.verified_at ?? null,
|
||||
tags: payload.tags ?? [],
|
||||
if_sync_version: payload.if_sync_version ?? null,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function clearAccountUpsertCache(): void {
|
||||
accountUpsertCache.clear();
|
||||
accountUpsertCache.clear()
|
||||
}
|
||||
|
||||
function currentRequestDebugMode(): "main-process" | "renderer-devtools" {
|
||||
return canUseRendererDevtoolsProxy() ? "renderer-devtools" : "main-process";
|
||||
function currentRequestDebugMode(): 'main-process' | 'renderer-devtools' {
|
||||
return canUseRendererDevtoolsProxy() ? 'renderer-devtools' : 'main-process'
|
||||
}
|
||||
|
||||
function resolveDesktopURL(path: string): string {
|
||||
return new URL(path, transportSession.baseURL).toString();
|
||||
return new URL(path, transportSession.baseURL).toString()
|
||||
}
|
||||
|
||||
export function resolveDesktopApiURL(path: string): string {
|
||||
return resolveDesktopURL(path);
|
||||
return resolveDesktopURL(path)
|
||||
}
|
||||
|
||||
export async function fetchDesktopApiURL(input: string | URL, init: RequestInit = {}): Promise<Response> {
|
||||
const headers = new Headers(init.headers);
|
||||
if (transportSession.clientToken && !headers.has("Authorization")) {
|
||||
headers.set("Authorization", `Bearer ${transportSession.clientToken}`);
|
||||
export async function fetchDesktopApiURL(
|
||||
input: string | URL,
|
||||
init: RequestInit = {},
|
||||
): Promise<Response> {
|
||||
const headers = new Headers(init.headers)
|
||||
if (transportSession.clientToken && !headers.has('Authorization')) {
|
||||
headers.set('Authorization', `Bearer ${transportSession.clientToken}`)
|
||||
}
|
||||
|
||||
const url =
|
||||
@@ -208,56 +209,56 @@ export async function fetchDesktopApiURL(input: string | URL, init: RequestInit
|
||||
? input.toString()
|
||||
: /^https?:\/\//i.test(input)
|
||||
? input
|
||||
: resolveDesktopURL(input);
|
||||
: resolveDesktopURL(input)
|
||||
return fetch(url, {
|
||||
...init,
|
||||
headers,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function resolveObservedRequestURL(path: string | undefined, baseURL: string): string {
|
||||
if (!path) {
|
||||
return baseURL;
|
||||
return baseURL
|
||||
}
|
||||
|
||||
try {
|
||||
return new URL(path, baseURL).toString();
|
||||
return new URL(path, baseURL).toString()
|
||||
} catch {
|
||||
return path;
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
function shouldFallbackToMainProcess(error: unknown): boolean {
|
||||
if (error instanceof ApiClientError) {
|
||||
if (typeof error.status === "number" && error.status > 0) {
|
||||
return false;
|
||||
if (typeof error.status === 'number' && error.status > 0) {
|
||||
return false
|
||||
}
|
||||
return /Failed to fetch|renderer_devtools_proxy_/i.test(error.message);
|
||||
return /Failed to fetch|renderer_devtools_proxy_/i.test(error.message)
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return /Failed to fetch|renderer_devtools_proxy_/i.test(error.message);
|
||||
return /Failed to fetch|renderer_devtools_proxy_/i.test(error.message)
|
||||
}
|
||||
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
async function proxyDesktopRequest<T, B = unknown>(
|
||||
method: "GET" | "POST" | "DELETE",
|
||||
method: 'GET' | 'POST' | 'DELETE',
|
||||
path: string,
|
||||
body?: B,
|
||||
): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/json",
|
||||
};
|
||||
Accept: 'application/json',
|
||||
}
|
||||
|
||||
let serializedBody: string | undefined;
|
||||
let serializedBody: string | undefined
|
||||
if (body !== undefined) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
serializedBody = JSON.stringify(body);
|
||||
headers['Content-Type'] = 'application/json'
|
||||
serializedBody = JSON.stringify(body)
|
||||
}
|
||||
if (transportSession.clientToken) {
|
||||
headers.Authorization = `Bearer ${transportSession.clientToken}`;
|
||||
headers.Authorization = `Bearer ${transportSession.clientToken}`
|
||||
}
|
||||
|
||||
const response = await rendererDevtoolsFetch({
|
||||
@@ -265,148 +266,150 @@ async function proxyDesktopRequest<T, B = unknown>(
|
||||
method,
|
||||
headers,
|
||||
body: serializedBody,
|
||||
});
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
throw new ApiClientError({ message: response.error });
|
||||
throw new ApiClientError({ message: response.error })
|
||||
}
|
||||
|
||||
let payload: ApiEnvelope<T> | Partial<ApiErrorEnvelope> | null = null;
|
||||
let payload: ApiEnvelope<T> | Partial<ApiErrorEnvelope> | null = null
|
||||
if (response.bodyText) {
|
||||
try {
|
||||
payload = JSON.parse(response.bodyText) as ApiEnvelope<T> | Partial<ApiErrorEnvelope>;
|
||||
payload = JSON.parse(response.bodyText) as ApiEnvelope<T> | Partial<ApiErrorEnvelope>
|
||||
} catch {
|
||||
payload = null;
|
||||
payload = null
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorPayload = payload as Partial<ApiErrorEnvelope> | null;
|
||||
const errorPayload = payload as Partial<ApiErrorEnvelope> | null
|
||||
throw new ApiClientError({
|
||||
message: errorPayload?.message ?? `desktop_http_${response.status}`,
|
||||
code: errorPayload?.code ?? 50000,
|
||||
status: response.status,
|
||||
detail: errorPayload?.detail,
|
||||
requestId: errorPayload?.request_id,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
if (!payload || typeof payload !== "object" || !("data" in payload)) {
|
||||
if (!payload || typeof payload !== 'object' || !('data' in payload)) {
|
||||
throw new ApiClientError({
|
||||
message: "desktop_invalid_api_envelope",
|
||||
message: 'desktop_invalid_api_envelope',
|
||||
status: response.status,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
return payload.data as T;
|
||||
return payload.data as T
|
||||
}
|
||||
|
||||
async function desktopGet<T>(path: string): Promise<T> {
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
if (transportState.requestDebugMode === "renderer-devtools") {
|
||||
transportState.requestDebugMode = currentRequestDebugMode()
|
||||
if (transportState.requestDebugMode === 'renderer-devtools') {
|
||||
try {
|
||||
return await proxyDesktopRequest<T>("GET", path);
|
||||
return await proxyDesktopRequest<T>('GET', path)
|
||||
} catch (error) {
|
||||
if (!shouldFallbackToMainProcess(error)) {
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
transportState.requestDebugMode = "main-process";
|
||||
transportState.requestDebugMode = 'main-process'
|
||||
}
|
||||
}
|
||||
return getDesktopApiClient().get<T>(path);
|
||||
return getDesktopApiClient().get<T>(path)
|
||||
}
|
||||
|
||||
async function desktopPost<T, B = unknown>(path: string, body?: B): Promise<T> {
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
if (transportState.requestDebugMode === "renderer-devtools") {
|
||||
transportState.requestDebugMode = currentRequestDebugMode()
|
||||
if (transportState.requestDebugMode === 'renderer-devtools') {
|
||||
try {
|
||||
return await proxyDesktopRequest<T, B>("POST", path, body);
|
||||
return await proxyDesktopRequest<T, B>('POST', path, body)
|
||||
} catch (error) {
|
||||
if (!shouldFallbackToMainProcess(error)) {
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
transportState.requestDebugMode = "main-process";
|
||||
transportState.requestDebugMode = 'main-process'
|
||||
}
|
||||
}
|
||||
return getDesktopApiClient().post<T, B>(path, body);
|
||||
return getDesktopApiClient().post<T, B>(path, body)
|
||||
}
|
||||
|
||||
async function desktopDelete<T>(path: string): Promise<T> {
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
if (transportState.requestDebugMode === "renderer-devtools") {
|
||||
transportState.requestDebugMode = currentRequestDebugMode()
|
||||
if (transportState.requestDebugMode === 'renderer-devtools') {
|
||||
try {
|
||||
return await proxyDesktopRequest<T>("DELETE", path);
|
||||
return await proxyDesktopRequest<T>('DELETE', path)
|
||||
} catch (error) {
|
||||
if (!shouldFallbackToMainProcess(error)) {
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
transportState.requestDebugMode = "main-process";
|
||||
transportState.requestDebugMode = 'main-process'
|
||||
}
|
||||
}
|
||||
return getDesktopApiClient().remove<T>(path);
|
||||
return getDesktopApiClient().remove<T>(path)
|
||||
}
|
||||
|
||||
export function initTransport(): ApiClient {
|
||||
if (desktopApiClient) {
|
||||
return desktopApiClient;
|
||||
return desktopApiClient
|
||||
}
|
||||
|
||||
transportState.initializedAt = transportState.initializedAt || Date.now();
|
||||
transportState.baseURL = transportSession.baseURL;
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
desktopApiClient = createDesktopClient(transportSession.baseURL);
|
||||
return desktopApiClient;
|
||||
transportState.initializedAt = transportState.initializedAt || Date.now()
|
||||
transportState.baseURL = transportSession.baseURL
|
||||
transportState.requestDebugMode = currentRequestDebugMode()
|
||||
desktopApiClient = createDesktopClient(transportSession.baseURL)
|
||||
return desktopApiClient
|
||||
}
|
||||
|
||||
export function configureTransport(session: DesktopRuntimeSessionSyncRequest | null): ApiClient | null {
|
||||
export function configureTransport(
|
||||
session: DesktopRuntimeSessionSyncRequest | null,
|
||||
): ApiClient | null {
|
||||
if (!session) {
|
||||
transportSession = {
|
||||
baseURL: process.env.DESKTOP_API_BASE_URL ?? "http://localhost:8080",
|
||||
baseURL: process.env.DESKTOP_API_BASE_URL ?? 'http://localhost:8080',
|
||||
clientToken: null,
|
||||
};
|
||||
transportState.baseURL = transportSession.baseURL;
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
transportState.auth = "pending";
|
||||
transportState.dispatchState = "idle";
|
||||
desktopApiClient = null;
|
||||
clearAccountUpsertCache();
|
||||
return null;
|
||||
}
|
||||
transportState.baseURL = transportSession.baseURL
|
||||
transportState.requestDebugMode = currentRequestDebugMode()
|
||||
transportState.auth = 'pending'
|
||||
transportState.dispatchState = 'idle'
|
||||
desktopApiClient = null
|
||||
clearAccountUpsertCache()
|
||||
return null
|
||||
}
|
||||
|
||||
transportSession = {
|
||||
baseURL: session.api_base_url.trim() || "http://localhost:8080",
|
||||
baseURL: session.api_base_url.trim() || 'http://localhost:8080',
|
||||
clientToken: session.client_token,
|
||||
};
|
||||
transportState.baseURL = transportSession.baseURL;
|
||||
transportState.requestDebugMode = currentRequestDebugMode();
|
||||
transportState.auth = session.client_token ? "registered" : "pending";
|
||||
transportState.dispatchState = session.client_token ? "connecting" : "idle";
|
||||
transportState.initializedAt = Date.now();
|
||||
desktopApiClient = createDesktopClient(transportSession.baseURL);
|
||||
clearAccountUpsertCache();
|
||||
return desktopApiClient;
|
||||
}
|
||||
transportState.baseURL = transportSession.baseURL
|
||||
transportState.requestDebugMode = currentRequestDebugMode()
|
||||
transportState.auth = session.client_token ? 'registered' : 'pending'
|
||||
transportState.dispatchState = session.client_token ? 'connecting' : 'idle'
|
||||
transportState.initializedAt = Date.now()
|
||||
desktopApiClient = createDesktopClient(transportSession.baseURL)
|
||||
clearAccountUpsertCache()
|
||||
return desktopApiClient
|
||||
}
|
||||
|
||||
export function setTransportAuthState(next: TransportAuthState): void {
|
||||
transportState.auth = next;
|
||||
transportState.auth = next
|
||||
}
|
||||
|
||||
export function setTransportDispatchState(next: TransportDispatchState): void {
|
||||
transportState.dispatchState = next;
|
||||
transportState.dispatchState = next
|
||||
}
|
||||
|
||||
export function noteTransportHeartbeat(success: boolean): void {
|
||||
transportState.lastHeartbeatAt = Date.now();
|
||||
transportState.lastHeartbeatStatus = success ? "success" : "failed";
|
||||
transportState.lastHeartbeatAt = Date.now()
|
||||
transportState.lastHeartbeatStatus = success ? 'success' : 'failed'
|
||||
}
|
||||
|
||||
export function noteTransportPull(success: boolean): void {
|
||||
transportState.lastPullAt = Date.now();
|
||||
transportState.lastPullStatus = success ? "success" : "failed";
|
||||
transportState.lastPullAt = Date.now()
|
||||
transportState.lastPullStatus = success ? 'success' : 'failed'
|
||||
}
|
||||
|
||||
export function getDesktopApiClient(): ApiClient {
|
||||
return desktopApiClient ?? initTransport();
|
||||
return desktopApiClient ?? initTransport()
|
||||
}
|
||||
|
||||
export function getTransportSnapshot() {
|
||||
@@ -414,105 +417,102 @@ export function getTransportSnapshot() {
|
||||
...transportState,
|
||||
hasClient: desktopApiClient !== null,
|
||||
hasClientToken: Boolean(transportSession.clientToken),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function heartbeatDesktopClient(
|
||||
payload: DesktopClientHeartbeatRequest,
|
||||
): Promise<DesktopClientHeartbeatResponse> {
|
||||
return desktopPost<DesktopClientHeartbeatResponse, DesktopClientHeartbeatRequest>(
|
||||
"/api/desktop/clients/heartbeat",
|
||||
'/api/desktop/clients/heartbeat',
|
||||
payload,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function offlineDesktopClient(): Promise<void> {
|
||||
await desktopPost<{ server_time: string }, Record<string, never>>(
|
||||
"/api/desktop/clients/offline",
|
||||
'/api/desktop/clients/offline',
|
||||
{},
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function revokeDesktopClient(): Promise<void> {
|
||||
await desktopPost<{ id: string }, Record<string, never>>(
|
||||
"/api/desktop/clients/revoke",
|
||||
{},
|
||||
);
|
||||
await desktopPost<{ id: string }, Record<string, never>>('/api/desktop/clients/revoke', {})
|
||||
}
|
||||
|
||||
export async function listDesktopAccounts(): Promise<DesktopAccountInfo[]> {
|
||||
return desktopGet<DesktopAccountInfo[]>("/api/desktop/accounts");
|
||||
return desktopGet<DesktopAccountInfo[]>('/api/desktop/accounts')
|
||||
}
|
||||
|
||||
export async function getDesktopArticleContent(articleId: number): Promise<DesktopArticleContent> {
|
||||
return desktopGet<DesktopArticleContent>(`/api/desktop/content/articles/${articleId}`);
|
||||
return desktopGet<DesktopArticleContent>(`/api/desktop/content/articles/${articleId}`)
|
||||
}
|
||||
|
||||
export async function listDesktopPublishTasks(
|
||||
params: ListDesktopPublishTasksParams = {},
|
||||
): Promise<DesktopPublishTaskListResponse> {
|
||||
const search = new URLSearchParams();
|
||||
const search = new URLSearchParams()
|
||||
if (params.page) {
|
||||
search.set("page", String(params.page));
|
||||
search.set('page', String(params.page))
|
||||
}
|
||||
if (params.page_size) {
|
||||
search.set("page_size", String(params.page_size));
|
||||
search.set('page_size', String(params.page_size))
|
||||
}
|
||||
if (params.title?.trim()) {
|
||||
search.set("title", params.title.trim());
|
||||
search.set('title', params.title.trim())
|
||||
}
|
||||
|
||||
const query = search.toString();
|
||||
const suffix = query ? `?${query}` : "";
|
||||
return desktopGet<DesktopPublishTaskListResponse>(`/api/desktop/publish-tasks${suffix}`);
|
||||
const query = search.toString()
|
||||
const suffix = query ? `?${query}` : ''
|
||||
return desktopGet<DesktopPublishTaskListResponse>(`/api/desktop/publish-tasks${suffix}`)
|
||||
}
|
||||
|
||||
export async function retryDesktopPublishTask(taskId: string): Promise<CreatePublishJobResponse> {
|
||||
return desktopPost<CreatePublishJobResponse, Record<string, never>>(
|
||||
`/api/desktop/publish-tasks/${encodeURIComponent(taskId)}/retry`,
|
||||
{},
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function upsertDesktopAccount(
|
||||
payload: UpsertDesktopAccountRequest,
|
||||
): Promise<DesktopAccountInfo> {
|
||||
const now = Date.now();
|
||||
const cacheKey = accountUpsertCacheKey(payload);
|
||||
const signature = accountUpsertSignature(payload);
|
||||
const cached = accountUpsertCache.get(cacheKey);
|
||||
const now = Date.now()
|
||||
const cacheKey = accountUpsertCacheKey(payload)
|
||||
const signature = accountUpsertSignature(payload)
|
||||
const cached = accountUpsertCache.get(cacheKey)
|
||||
if (cached && cached.signature === signature && cached.expiresAt > now) {
|
||||
if (cached.inFlight) {
|
||||
return cached.inFlight;
|
||||
return cached.inFlight
|
||||
}
|
||||
return cached.response;
|
||||
return cached.response
|
||||
}
|
||||
|
||||
const request = desktopPost<DesktopAccountInfo, UpsertDesktopAccountRequest>(
|
||||
"/api/desktop/accounts",
|
||||
'/api/desktop/accounts',
|
||||
payload,
|
||||
);
|
||||
)
|
||||
accountUpsertCache.set(cacheKey, {
|
||||
signature,
|
||||
expiresAt: now + ACCOUNT_UPSERT_CACHE_TTL_MS,
|
||||
response: cached?.response ?? ({} as DesktopAccountInfo),
|
||||
inFlight: request,
|
||||
});
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await request;
|
||||
const response = await request
|
||||
accountUpsertCache.set(cacheKey, {
|
||||
signature,
|
||||
expiresAt: Date.now() + ACCOUNT_UPSERT_CACHE_TTL_MS,
|
||||
response,
|
||||
inFlight: null,
|
||||
});
|
||||
return response;
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
if (accountUpsertCache.get(cacheKey)?.inFlight === request) {
|
||||
accountUpsertCache.delete(cacheKey);
|
||||
accountUpsertCache.delete(cacheKey)
|
||||
}
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,32 +520,32 @@ export async function reportDesktopAccountHealth(
|
||||
payload: ReportDesktopAccountHealthRequest,
|
||||
): Promise<ReportDesktopAccountHealthResponse> {
|
||||
return desktopPost<ReportDesktopAccountHealthResponse, ReportDesktopAccountHealthRequest>(
|
||||
"/api/desktop/accounts/health-reports",
|
||||
'/api/desktop/accounts/health-reports',
|
||||
payload,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function deleteDesktopAccount(
|
||||
desktopId: string,
|
||||
ifSyncVersion: number,
|
||||
): Promise<DesktopAccountInfo> {
|
||||
const params = new URLSearchParams({ if_sync_version: String(ifSyncVersion) });
|
||||
const params = new URLSearchParams({ if_sync_version: String(ifSyncVersion) })
|
||||
const response = await desktopDelete<DesktopAccountInfo>(
|
||||
`/api/desktop/accounts/${encodeURIComponent(desktopId)}?${params.toString()}`,
|
||||
);
|
||||
clearAccountUpsertCache();
|
||||
return response;
|
||||
)
|
||||
clearAccountUpsertCache()
|
||||
return response
|
||||
}
|
||||
|
||||
export async function leaseDesktopTask(
|
||||
request: LeaseDesktopTaskRequest,
|
||||
): Promise<LeaseDesktopTaskResponse> {
|
||||
const taskId = request.task_id?.trim();
|
||||
const path = taskId ? `/api/desktop/tasks/${taskId}/lease` : "/api/desktop/tasks/lease";
|
||||
const taskId = request.task_id?.trim()
|
||||
const path = taskId ? `/api/desktop/tasks/${taskId}/lease` : '/api/desktop/tasks/lease'
|
||||
return desktopPost<LeaseDesktopTaskResponse, LeaseDesktopTaskRequest>(
|
||||
path,
|
||||
taskId ? { kind: request.kind } : request,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function extendDesktopTask(
|
||||
@@ -555,7 +555,7 @@ export async function extendDesktopTask(
|
||||
return desktopPost<DesktopTaskInfo, ExtendDesktopTaskRequest>(
|
||||
`/api/desktop/tasks/${taskId}/extend`,
|
||||
payload,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function completeDesktopTask(
|
||||
@@ -565,7 +565,7 @@ export async function completeDesktopTask(
|
||||
return desktopPost<DesktopTaskInfo, CompleteDesktopTaskRequest>(
|
||||
`/api/desktop/tasks/${taskId}/result`,
|
||||
payload,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function cancelDesktopTask(
|
||||
@@ -575,25 +575,25 @@ export async function cancelDesktopTask(
|
||||
return desktopPost<DesktopTaskInfo, CancelDesktopTaskRequest>(
|
||||
`/api/desktop/tasks/${taskId}/cancel`,
|
||||
payload,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function leaseMonitoringTasks(
|
||||
payload: MonitoringLeaseTasksPayload = {},
|
||||
): Promise<MonitoringLeaseTasksResponse> {
|
||||
return desktopPost<MonitoringLeaseTasksResponse, MonitoringLeaseTasksPayload>(
|
||||
"/api/desktop/monitoring/tasks/lease",
|
||||
'/api/desktop/monitoring/tasks/lease',
|
||||
payload,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function resumeMonitoringTasks(
|
||||
payload: MonitoringResumeTasksPayload = {},
|
||||
): Promise<MonitoringResumeTasksResponse> {
|
||||
return desktopPost<MonitoringResumeTasksResponse, MonitoringResumeTasksPayload>(
|
||||
"/api/desktop/monitoring/tasks/resume",
|
||||
'/api/desktop/monitoring/tasks/resume',
|
||||
payload,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function submitMonitoringTaskResult(
|
||||
@@ -603,7 +603,7 @@ export async function submitMonitoringTaskResult(
|
||||
return desktopPost<MonitoringTaskResultResponse, MonitoringTaskResultPayload>(
|
||||
`/api/desktop/monitoring/tasks/${taskId}/result`,
|
||||
payload,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function skipMonitoringTask(
|
||||
@@ -613,7 +613,7 @@ export async function skipMonitoringTask(
|
||||
return desktopPost<MonitoringSkipTaskResponse, MonitoringSkipTaskPayload>(
|
||||
`/api/desktop/monitoring/tasks/${taskId}/skip`,
|
||||
payload,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function cancelMonitoringTask(
|
||||
@@ -623,5 +623,5 @@ export async function cancelMonitoringTask(
|
||||
return desktopPost<MonitoringCancelTaskResponse, MonitoringCancelTaskPayload>(
|
||||
`/api/desktop/monitoring/tasks/${taskId}/cancel`,
|
||||
payload,
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user