import type { RelayEvent } from "@/shared/api/types"; /** * Observable connection state for the relay singleton. * * - `idle` — never tried to connect yet (post-init, pre-workspace). * - `connecting` — initial socket + AUTH handshake in flight. * - `connected` — socket open and AUTH'd. * - `reconnecting` — socket dropped, waiting for the backoff timer. * - `stalled` — socket is *open* per the WS layer but no inbound frames * for a long time (half-open / Warp split-brain). We * surface this so the UI can warn even though tungstenite * hasn't reported anything wrong yet. * - `disconnected` — final/terminal disconnect (auth rejected, workspace * switch, etc.) — no auto-reconnect scheduled. */ export type ConnectionState = | "idle" | "connecting" | "connected" | "reconnecting" | "stalled" | "disconnected"; /** True when the UI should surface a "connection lost" indicator. */ export function isRelayConnectionDegraded(state: ConnectionState): boolean { return ( state === "reconnecting" || state === "stalled" || state === "disconnected" ); } export type RelaySubscriptionFilter = { kinds: number[]; limit: number; authors?: string[]; since?: number; until?: number; } & Partial>; type HistorySubscription = { mode: "history"; events: RelayEvent[]; resolve: (events: RelayEvent[]) => void; reject: (error: Error) => void; timeout: number; }; type LiveSubscription = { mode: "live"; filter: RelaySubscriptionFilter; onEvent: (event: RelayEvent) => void; resolveReady?: () => void; lastSeenCreatedAt?: number; }; export type PendingEvent = { event: RelayEvent; resolve: (event: RelayEvent) => void; reject: (error: Error) => void; timeout: number; }; export type RelaySubscription = HistorySubscription | LiveSubscription; export function sortEvents(events: RelayEvent[]) { return [...events].sort((left, right) => left.created_at - right.created_at); } export function getTextPayload(message: unknown) { if (typeof message === "string") { return message; } if ( typeof message === "object" && message !== null && "type" in message && message.type === "Text" && "data" in message && typeof message.data === "string" ) { return message.data; } if ( typeof message === "object" && message !== null && "Text" in message && typeof message.Text === "string" ) { return message.Text; } return null; }