mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-15 04:40:26 +02:00
25a98a9869
Allow local-operator DM invite import without requiring a full admin session. Prioritize bundled/bootstrap seed peers and shorten stale seed cooldowns for faster Infonet recovery. Replace raw DM invite dumps with copyable signed-address controls, contact request handling, and safer sealed-send behavior while the private delivery route connects.
331 lines
8.6 KiB
TypeScript
331 lines
8.6 KiB
TypeScript
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<WormholeState> | null;
|
|
}
|
|
| null = null;
|
|
let wormholeSettingsCache:
|
|
| {
|
|
value: WormholeSettingsSnapshot;
|
|
expiresAt: number;
|
|
inflight: Promise<WormholeSettingsSnapshot> | null;
|
|
}
|
|
| null = null;
|
|
|
|
async function parseState(res: Response): Promise<WormholeState> {
|
|
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<WormholeState> {
|
|
const res = await fetch(`${API_BASE}/api/wormhole/status`, { cache: 'no-store' });
|
|
return parseState(res);
|
|
}
|
|
|
|
async function loadWormholeSettings(): Promise<WormholeSettingsSnapshot> {
|
|
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<WormholeState> {
|
|
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<WormholeSettingsSnapshot> {
|
|
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<WormholeState> {
|
|
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<PrivateDeliveryActionResponse> {
|
|
const response = await controlPlaneJson<PrivateDeliveryActionResponse>(
|
|
`/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<WormholeState> {
|
|
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<WormholeState> {
|
|
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<WormholeJoinResponse> {
|
|
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<WormholeJoinResponse> {
|
|
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;
|
|
}
|