export type FrpcDownloadProgress = { phase: 'downloading' | 'extracting'; url?: string; bytesReceived?: number; totalBytes?: number | null; percent?: number | null; }; export type TunnelStartPhase = 'preparing_frpc' | 'registering' | 'starting_frpc' | 'reconnecting_frpc'; export type TunnelStartProgress = { phase: TunnelStartPhase; startedAt: string; publicUrl?: string | null; }; export type TunnelRegistration = { tunnelId: string; tunnelToken: string; subdomain: string; publicUrl: string; frpc: { serverAddr: string; serverPort: number; authToken: string; proxyName: string; }; expiresAt: string; heartbeatIntervalMs: number; }; export type PersistedTunnelState = { tunnelId: string; tunnelToken: string; subdomain: string; publicUrl: string; frpcAuthToken: string; registeredAt: string; enabled?: boolean; /** Saved on register so stop/start can resume without re-registering. */ frpcServerAddr?: string; frpcServerPort?: number; proxyName?: string; heartbeatIntervalMs?: number; }; export type TunnelStatus = { enabled: boolean; state: 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error'; subdomain: string | null; publicUrl: string | null; connectedSince: string | null; frpcPid: number | null; lastHeartbeatAt: string | null; lastError: string | null; frpcDownload?: FrpcDownloadProgress | null; startProgress?: TunnelStartProgress | null; config: { autoStart: boolean; brokerUrl: string; transport: { tls: 'broker_terminated'; }; }; }; export type TunnelQrPayload = { qrPayload: string; publicUrl: string | null; lanUrl: string | null; expiresAt?: string; };