{"version":3,"file":"websocket-client.mjs","sources":["../../../src/lib/realtime/websocket-client.ts"],"sourcesContent":["/**\n * @file WebSocket Client\n * @description Low-level WebSocket connection manager with retries\n */\n\nimport { env } from '../../config/env';\n\n/**\n * WebSocket connection state\n */\nexport type WebSocketState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting';\n\n/**\n * WebSocket message handler\n */\nexport type MessageHandler = (data: unknown) => void;\n\n/**\n * WebSocket error handler\n */\nexport type ErrorHandler = (error: Event) => void;\n\n/**\n * WebSocket state change handler\n */\nexport type StateChangeHandler = (state: WebSocketState) => void;\n\n/**\n * WebSocket client configuration\n */\nexport interface WebSocketClientConfig {\n  url: string;\n  reconnect?: boolean;\n  reconnectInterval?: number;\n  maxReconnectAttempts?: number;\n  heartbeatInterval?: number;\n  protocols?: string[];\n}\n\n/**\n * Default configuration\n */\nconst defaultConfig: Partial<WebSocketClientConfig> = {\n  reconnect: true,\n  reconnectInterval: 3000,\n  maxReconnectAttempts: 10,\n  heartbeatInterval: 30000,\n};\n\n/**\n * WebSocket client class\n */\nexport class WebSocketClient {\n  private socket: WebSocket | null = null;\n  private config: WebSocketClientConfig;\n  private state: WebSocketState = 'disconnected';\n  private reconnectAttempts = 0;\n  private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;\n  private heartbeatInterval: ReturnType<typeof setInterval> | null = null;\n\n  private messageHandlers: Set<MessageHandler> = new Set();\n  private errorHandlers: Set<ErrorHandler> = new Set();\n  private stateChangeHandlers: Set<StateChangeHandler> = new Set();\n  private channelHandlers: Map<string, Set<MessageHandler>> = new Map();\n\n  constructor(config: WebSocketClientConfig) {\n    this.config = { ...defaultConfig, ...config };\n  }\n\n  /**\n   * Get current connection state\n   */\n  getState(): WebSocketState {\n    return this.state;\n  }\n\n  /**\n   * Check if connected\n   */\n  isConnected(): boolean {\n    return this.state === 'connected' && this.socket?.readyState === WebSocket.OPEN;\n  }\n\n  /**\n   * Connect to WebSocket server\n   */\n  connect(): void {\n    if (this.socket?.readyState === WebSocket.OPEN) {\n      return;\n    }\n\n    this.setState('connecting');\n\n    try {\n      this.socket = new WebSocket(this.config.url, this.config.protocols);\n      this.setupEventListeners();\n    } catch (error) {\n      console.error('[WebSocket] Connection error:', error);\n      this.handleDisconnect();\n    }\n  }\n\n  /**\n   * Disconnect from WebSocket server\n   */\n  disconnect(): void {\n    this.clearHeartbeat();\n    this.clearReconnectTimeout();\n\n    if (this.socket) {\n      this.socket.close(1000, 'Client disconnect');\n      this.socket = null;\n    }\n\n    this.setState('disconnected');\n    this.reconnectAttempts = 0;\n  }\n\n  /**\n   * Send message to server\n   */\n  send(data: unknown): void {\n    if (!this.isConnected()) {\n      console.warn('[WebSocket] Cannot send message: not connected');\n      return;\n    }\n\n    try {\n      const message = typeof data === 'string' ? data : JSON.stringify(data);\n      if (this.socket !== null) {\n        this.socket.send(message);\n      }\n    } catch (error) {\n      console.error('[WebSocket] Send error:', error);\n    }\n  }\n\n  /**\n   * Send message to specific channel\n   */\n  sendToChannel(channel: string, data: unknown): void {\n    this.send({ channel, data });\n  }\n\n  /**\n   * Subscribe to all messages\n   */\n  onMessage(handler: MessageHandler): () => void {\n    this.messageHandlers.add(handler);\n    return () => this.messageHandlers.delete(handler);\n  }\n\n  /**\n   * Subscribe to specific channel\n   */\n  subscribe(channel: string, handler: MessageHandler): () => void {\n    if (!this.channelHandlers.has(channel)) {\n      this.channelHandlers.set(channel, new Set());\n      // Notify server of subscription\n      this.send({ type: 'subscribe', channel });\n    }\n\n    const handlers = this.channelHandlers.get(channel);\n    if (handlers !== undefined) {\n      handlers.add(handler);\n    }\n\n    return () => {\n      const handlers = this.channelHandlers.get(channel);\n      if (handlers) {\n        handlers.delete(handler);\n        if (handlers.size === 0) {\n          this.channelHandlers.delete(channel);\n          // Notify server of unsubscription\n          this.send({ type: 'unsubscribe', channel });\n        }\n      }\n    };\n  }\n\n  /**\n   * Subscribe to errors\n   */\n  onError(handler: ErrorHandler): () => void {\n    this.errorHandlers.add(handler);\n    return () => this.errorHandlers.delete(handler);\n  }\n\n  /**\n   * Subscribe to state changes\n   */\n  onStateChange(handler: StateChangeHandler): () => void {\n    this.stateChangeHandlers.add(handler);\n    return () => this.stateChangeHandlers.delete(handler);\n  }\n\n  /**\n   * Setup WebSocket event listeners\n   */\n  private setupEventListeners(): void {\n    if (!this.socket) return;\n\n    this.socket.onopen = () => {\n      this.setState('connected');\n      this.reconnectAttempts = 0;\n      this.startHeartbeat();\n\n      // Resubscribe to channels\n      this.channelHandlers.forEach((_, channel) => {\n        this.send({ type: 'subscribe', channel });\n      });\n    };\n\n    this.socket.onmessage = (event) => {\n      try {\n        const data = JSON.parse(event.data as string) as {\n          type?: string;\n          channel?: string;\n          data?: unknown;\n        };\n\n        // Handle heartbeat response\n        if (data.type === 'pong') {\n          return;\n        }\n\n        // Route to channel handlers\n        if (data.channel !== undefined && this.channelHandlers.has(data.channel)) {\n          const handlers = this.channelHandlers.get(data.channel);\n          handlers?.forEach((handler) => {\n            handler(data.data);\n          });\n        }\n\n        // Notify all message handlers\n        this.messageHandlers.forEach((handler) => handler(data));\n      } catch {\n        // Handle non-JSON messages\n        this.messageHandlers.forEach((handler) => handler(event.data));\n      }\n    };\n\n    this.socket.onerror = (event) => {\n      console.error('[WebSocket] Error:', event);\n      this.errorHandlers.forEach((handler) => handler(event));\n    };\n\n    this.socket.onclose = (event) => {\n      console.info('[WebSocket] Closed:', event.code, event.reason);\n      this.handleDisconnect();\n    };\n  }\n\n  /**\n   * Handle disconnection\n   */\n  private handleDisconnect(): void {\n    this.clearHeartbeat();\n    this.socket = null;\n\n    if (\n      this.config.reconnect === true &&\n      this.reconnectAttempts < (this.config.maxReconnectAttempts ?? 10)\n    ) {\n      this.setState('reconnecting');\n      this.scheduleReconnect();\n    } else {\n      this.setState('disconnected');\n    }\n  }\n\n  /**\n   * Schedule reconnection\n   */\n  private scheduleReconnect(): void {\n    this.clearReconnectTimeout();\n\n    const delay = this.calculateBackoff();\n    this.reconnectAttempts++;\n\n    console.info(`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n\n    this.reconnectTimeout = setTimeout(() => {\n      this.connect();\n    }, delay);\n  }\n\n  /**\n   * Calculate exponential backoff delay\n   */\n  private calculateBackoff(): number {\n    const base = this.config.reconnectInterval ?? 3000;\n    const delay = base * Math.pow(1.5, this.reconnectAttempts);\n    const jitter = Math.random() * 1000;\n    return Math.min(delay + jitter, 30000); // Max 30 seconds\n  }\n\n  /**\n   * Start heartbeat interval\n   */\n  private startHeartbeat(): void {\n    this.clearHeartbeat();\n\n    if (this.config.heartbeatInterval != null && this.config.heartbeatInterval > 0) {\n      this.heartbeatInterval = setInterval(() => {\n        if (this.isConnected()) {\n          this.send({ type: 'ping' });\n        }\n      }, this.config.heartbeatInterval);\n    }\n  }\n\n  /**\n   * Clear heartbeat interval\n   */\n  private clearHeartbeat(): void {\n    if (this.heartbeatInterval) {\n      clearInterval(this.heartbeatInterval);\n      this.heartbeatInterval = null;\n    }\n  }\n\n  /**\n   * Clear reconnect timeout\n   */\n  private clearReconnectTimeout(): void {\n    if (this.reconnectTimeout) {\n      clearTimeout(this.reconnectTimeout);\n      this.reconnectTimeout = null;\n    }\n  }\n\n  /**\n   * Update and broadcast state\n   */\n  private setState(state: WebSocketState): void {\n    this.state = state;\n    this.stateChangeHandlers.forEach((handler) => handler(state));\n  }\n}\n\n/**\n * Create WebSocket client with default configuration\n */\nexport function createWebSocketClient(\n  path: string = '/ws',\n  config?: Partial<WebSocketClientConfig>\n): WebSocketClient {\n  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n  const host = env.wsUrl || `${protocol}//${window.location.host}`;\n  const url = `${host}${path}`;\n\n  return new WebSocketClient({ url, ...config });\n}\n"],"names":["defaultConfig","WebSocketClient","config","error","data","message","channel","handler","handlers","_","event","delay","jitter","state","createWebSocketClient","path","protocol","url","env"],"mappings":"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"}