import { API_BASE } from '@/lib/api'; import { controlPlaneFetch, controlPlaneJson } from '@/lib/controlPlane'; import type { LegacyCompatibilitySnapshot } from '@/mesh/wormholeCompatibility'; export interface WormholeState { installed: boolean; configured: boolean; running: boolean; ready: boolean; transport_tier?: string; transport_configured: string; transport_active: string; arti_ready?: boolean; proxy_active: string; last_error: string; started_at: number; pid: number; privacy_level_effective: string; reason: string; last_restart: number; last_start: number; transport: string; proxy: string; anonymous_mode?: boolean; anonymous_mode_ready?: boolean; rns_enabled?: boolean; rns_ready?: boolean; rns_configured_peers?: number; rns_active_peers?: number; rns_private_dm_direct_ready?: boolean; recent_private_clearnet_fallback?: boolean; recent_private_clearnet_fallback_at?: number; recent_private_clearnet_fallback_reason?: string; clearnet_fallback_policy?: string; clearnet_fallback_requested?: string; private_delivery?: PrivateDeliverySummary; legacy_compatibility?: LegacyCompatibilitySnapshot; } export interface PrivateDeliveryApprovalAction { code: 'wait' | 'relay'; label: string; emphasis: 'primary' | 'secondary' | ''; } export interface PrivateDeliveryApprovalState { required?: boolean; reason_code?: string; started_at?: number; window_seconds?: number; status_label?: string; detail?: string; actions?: PrivateDeliveryApprovalAction[]; } export interface PrivateDeliveryItem { id: string; lane: string; release_state: string; required_tier?: string; current_tier?: string; status?: { code?: string; label?: string; reason_code?: string; reason?: string; }; approval?: PrivateDeliveryApprovalState; } export interface PrivateDeliverySummary { pending_count?: number; preparing_count?: number; queued_count?: number; approval_required_count?: number; current_tier?: string; items?: PrivateDeliveryItem[]; } export interface PrivateDeliveryActionResponse { ok: boolean; action: 'wait' | 'relay'; item?: PrivateDeliveryItem; detail?: string; } export interface WormholeSettingsSnapshot { enabled?: boolean; transport?: string; socks_proxy?: string; socks_dns?: boolean; anonymous_mode?: boolean; privacy_profile?: string; } export interface WormholeJoinResponse { ok: boolean; identity?: { node_id: string; public_key: string; public_key_algo: string; }; runtime?: WormholeState; settings?: WormholeSettingsSnapshot; } const CACHE_TTL_MS = 15000; let wormholeStateCache: | { value: WormholeState; expiresAt: number; inflight: Promise | null; } | null = null; let wormholeSettingsCache: | { value: WormholeSettingsSnapshot; expiresAt: number; inflight: Promise | null; } | null = null; async function parseState(res: Response): Promise { if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data?.detail || data?.message || 'Wormhole request failed'); } return (await res.json()) as WormholeState; } function resetWormholeCaches(): void { wormholeStateCache = null; wormholeSettingsCache = null; } async function loadWormholeState(): Promise { const res = await fetch(`${API_BASE}/api/wormhole/status`, { cache: 'no-store' }); return parseState(res); } async function loadWormholeSettings(): Promise { const res = await fetch(`${API_BASE}/api/settings/wormhole`, { cache: 'no-store' }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data?.detail || data?.message || 'Wormhole settings request failed'); } return (await res.json()) as WormholeSettingsSnapshot; } export function invalidateWormholeRuntimeCache(): void { resetWormholeCaches(); } export async function fetchWormholeState(force: boolean = false): Promise { const now = Date.now(); if (!force && wormholeStateCache?.value && wormholeStateCache.expiresAt > now) { return wormholeStateCache.value; } if (!force && wormholeStateCache?.inflight) { return wormholeStateCache.inflight; } const inflight = loadWormholeState() .then((value) => { wormholeStateCache = { value, expiresAt: Date.now() + CACHE_TTL_MS, inflight: null, }; return value; }) .catch((error) => { if (wormholeStateCache) wormholeStateCache.inflight = null; throw error; }); wormholeStateCache = { value: wormholeStateCache?.value || ({} as WormholeState), expiresAt: 0, inflight, }; return inflight; } export async function fetchWormholeSettings( force: boolean = false, ): Promise { const now = Date.now(); if (!force && wormholeSettingsCache?.value && wormholeSettingsCache.expiresAt > now) { return wormholeSettingsCache.value; } if (!force && wormholeSettingsCache?.inflight) { return wormholeSettingsCache.inflight; } const inflight = loadWormholeSettings() .then((value) => { wormholeSettingsCache = { value, expiresAt: Date.now() + CACHE_TTL_MS, inflight: null, }; return value; }) .catch((error) => { if (wormholeSettingsCache) wormholeSettingsCache.inflight = null; throw error; }); wormholeSettingsCache = { value: wormholeSettingsCache?.value || {}, expiresAt: 0, inflight, }; return inflight; } export async function connectWormhole( options: { requireAdminSession?: boolean } = {}, ): Promise { resetWormholeCaches(); const res = await controlPlaneFetch('/api/wormhole/connect', { method: 'POST', requireAdminSession: options.requireAdminSession, }); const state = await parseState(res); wormholeStateCache = { value: state, expiresAt: Date.now() + CACHE_TTL_MS, inflight: null, }; return state; } export async function updatePrivateDeliveryAction( itemId: string, action: 'wait' | 'relay', ): Promise { const response = await controlPlaneJson( `/api/wormhole/private-delivery/${encodeURIComponent(itemId)}/action`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action }), }, ); invalidateWormholeRuntimeCache(); return response; } export async function disconnectWormhole(): Promise { resetWormholeCaches(); const res = await controlPlaneFetch('/api/wormhole/disconnect', { method: 'POST', }); const state = await parseState(res); wormholeStateCache = { value: state, expiresAt: Date.now() + CACHE_TTL_MS, inflight: null, }; return state; } export async function restartWormhole(): Promise { resetWormholeCaches(); const res = await controlPlaneFetch('/api/wormhole/restart', { method: 'POST', }); const state = await parseState(res); wormholeStateCache = { value: state, expiresAt: Date.now() + CACHE_TTL_MS, inflight: null, }; return state; } export async function joinWormhole(): Promise { resetWormholeCaches(); const res = await controlPlaneFetch('/api/wormhole/join', { method: 'POST', requireAdminSession: false, }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data?.detail || data?.message || 'Wormhole join failed'); } const data = (await res.json()) as WormholeJoinResponse; if (data?.runtime) { wormholeStateCache = { value: data.runtime, expiresAt: Date.now() + CACHE_TTL_MS, inflight: null, }; } if (data?.settings) { wormholeSettingsCache = { value: data.settings, expiresAt: Date.now() + CACHE_TTL_MS, inflight: null, }; } return data; } export async function leaveWormhole(): Promise { resetWormholeCaches(); const res = await controlPlaneFetch('/api/wormhole/leave', { method: 'POST', requireAdminSession: false, }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data?.detail || data?.message || 'Wormhole leave failed'); } const data = (await res.json()) as WormholeJoinResponse; if (data?.runtime) { wormholeStateCache = { value: data.runtime, expiresAt: Date.now() + CACHE_TTL_MS, inflight: null, }; } if (data?.settings) { wormholeSettingsCache = { value: data.settings, expiresAt: Date.now() + CACHE_TTL_MS, inflight: null, }; } return data; }