{"version":3,"sources":["../../src/client/sse.ts"],"names":["SSEConnectionStatus"],"mappings":";AA8BY,IAAA,mBAAA,qBAAAA,oBAAL,KAAA;AACL,EAAAA,qBAAA,cAAe,CAAA,GAAA,cAAA;AACf,EAAAA,qBAAA,YAAa,CAAA,GAAA,YAAA;AACb,EAAAA,qBAAA,WAAY,CAAA,GAAA,WAAA;AACZ,EAAAA,qBAAA,cAAe,CAAA,GAAA,cAAA;AACf,EAAAA,qBAAA,OAAQ,CAAA,GAAA,OAAA;AALE,EAAAA,OAAAA,oBAAAA;AAAA,CAAA,EAAA,mBAAA,IAAA,EAAA","file":"index.mjs","sourcesContent":["import { MCPOptions } from './types';\nimport { MCPError } from '../errors';\n\ntype EventCallback = (event: MessageEvent) => void;\n\n/**\n * Options for the SSE Client\n */\nexport interface SSEOptions extends MCPOptions {\n  // Endpoint for SSE events, defaults to \"/events\"\n  eventsEndpoint?: string;\n  // Timeout in ms before reconnecting after connection loss\n  reconnectTimeout?: number;\n  // Maximum number of reconnection attempts\n  maxReconnectAttempts?: number;\n  // Additional SSE-specific options\n  sseOptions?: EventSourceInit;\n  // Event types to listen for (used for filtering)\n  eventTypes?: string[];\n  // Heartbeat interval in ms (default: 30000)\n  heartbeatInterval?: number;\n  // Connection timeout in ms (default: 10000)\n  connectionTimeout?: number;\n  // Whether to enable auto-reconnect (default: true)\n  autoReconnect?: boolean;\n}\n\n/**\n * Connection status for SSE client\n */\nexport enum SSEConnectionStatus {\n  DISCONNECTED = 'disconnected',\n  CONNECTING = 'connecting',\n  CONNECTED = 'connected',\n  RECONNECTING = 'reconnecting',\n  ERROR = 'error'\n}\n\n/**\n * SSE Client for handling Server-Sent Events\n */\nexport class MCPSSEClient {\n  private baseUrl: string;\n  private apiKey?: string;\n  private token?: string;\n  private tenantId?: string;\n  private eventSource: EventSource | null = null;\n  private eventHandlers: Map<string, Set<EventCallback>> = new Map();\n  private reconnectTimeout: number;\n  private maxReconnectAttempts: number;\n  private reconnectAttempts: number = 0;\n  private eventsEndpoint: string;\n  private sseOptions: EventSourceInit | undefined;\n  private status: SSEConnectionStatus = SSEConnectionStatus.DISCONNECTED;\n  private eventTypes: string[] | undefined;\n  private lastEventId: string | null = null;\n  private heartbeatInterval: number;\n  private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n  private connectionTimeout: number;\n  private connectionTimer: ReturnType<typeof setTimeout> | null = null;\n  private autoReconnect: boolean;\n  private statusHandlers: Set<(status: SSEConnectionStatus) => void> = new Set();\n\n  /**\n   * Create a new SSE client\n   */\n  constructor(options: SSEOptions) {\n    this.baseUrl = options.baseUrl || 'https://api.mcp.ai';\n    this.apiKey = options.apiKey;\n    this.token = options.token;\n    this.tenantId = options.tenantId;\n    this.reconnectTimeout = options.reconnectTimeout || 3000;\n    this.maxReconnectAttempts = options.maxReconnectAttempts || 10;\n    this.eventsEndpoint = options.eventsEndpoint || '/events';\n    this.sseOptions = options.sseOptions;\n    this.eventTypes = options.eventTypes;\n    this.heartbeatInterval = options.heartbeatInterval || 30000;\n    this.connectionTimeout = options.connectionTimeout || 10000;\n    this.autoReconnect = options.autoReconnect !== false;\n  }\n\n  /**\n   * Get the current connection status\n   */\n  getStatus(): SSEConnectionStatus {\n    return this.status;\n  }\n\n  /**\n   * Register a handler for connection status changes\n   */\n  onStatusChange(handler: (status: SSEConnectionStatus) => void): () => void {\n    this.statusHandlers.add(handler);\n\n    // Call immediately with current status\n    handler(this.status);\n\n    // Return function to remove handler\n    return () => {\n      this.statusHandlers.delete(handler);\n    };\n  }\n\n  /**\n   * Update connection status and notify handlers\n   */\n  private setStatus(status: SSEConnectionStatus): void {\n    if (this.status !== status) {\n      this.status = status;\n\n      // Notify all status handlers\n      for (const handler of this.statusHandlers) {\n        try {\n          handler(status);\n        } catch (error) {\n          console.error('Error in SSE status handler:', error);\n        }\n      }\n    }\n  }\n\n  /**\n   * Connect to the SSE endpoint\n   */\n  connect(): void {\n    if (this.eventSource) {\n      this.disconnect();\n    }\n\n    this.setStatus(SSEConnectionStatus.CONNECTING);\n    this.reconnectAttempts = 0;\n\n    const url = new URL(`${this.baseUrl}${this.eventsEndpoint}`);\n\n    // Add auth parameters\n    if (this.apiKey) {\n      url.searchParams.append('apiKey', this.apiKey);\n    }\n    if (this.token) {\n      url.searchParams.append('token', this.token);\n    }\n    if (this.tenantId) {\n      url.searchParams.append('tenantId', this.tenantId);\n    }\n\n    // Add event types filter if specified\n    if (this.eventTypes && this.eventTypes.length > 0) {\n      url.searchParams.append('events', this.eventTypes.join(','));\n    }\n\n    // Add last event ID for resuming\n    if (this.lastEventId) {\n      url.searchParams.append('lastEventId', this.lastEventId);\n    }\n\n    try {\n      // Set connection timeout\n      this.connectionTimer = setTimeout(() => {\n        if (this.status === SSEConnectionStatus.CONNECTING) {\n          console.warn('SSE connection timeout');\n          this.setStatus(SSEConnectionStatus.ERROR);\n          this.disconnect();\n          if (this.autoReconnect) {\n            this.attemptReconnect();\n          }\n        }\n      }, this.connectionTimeout);\n\n      // Create the EventSource\n      this.eventSource = new EventSource(url.toString(), this.sseOptions);\n\n      // Set up event handlers\n      this.eventSource.onopen = this.handleOpen.bind(this);\n      this.eventSource.onerror = this.handleError.bind(this);\n      this.eventSource.onmessage = this.handleMessage.bind(this);\n\n      // Register for specific event types and existing handlers\n      if (this.eventTypes) {\n        for (const eventType of this.eventTypes) {\n          this.eventSource.addEventListener(eventType, this.handleEvent.bind(this));\n        }\n      }\n\n      // Register all event types from handlers\n      for (const eventType of this.eventHandlers.keys()) {\n        this.eventSource.addEventListener(eventType, this.handleEvent.bind(this));\n      }\n    } catch (error) {\n      console.error('Error connecting to SSE endpoint:', error);\n      this.handleConnectionError(error);\n    }\n  }\n\n  /**\n   * Disconnect from the SSE endpoint\n   */\n  disconnect(): void {\n    // Clear timers\n    if (this.connectionTimer) {\n      clearTimeout(this.connectionTimer);\n      this.connectionTimer = null;\n    }\n\n    if (this.heartbeatTimer) {\n      clearInterval(this.heartbeatTimer);\n      this.heartbeatTimer = null;\n    }\n\n    if (this.eventSource) {\n      this.eventSource.close();\n      this.eventSource = null;\n\n      if (this.status !== SSEConnectionStatus.ERROR) {\n        this.setStatus(SSEConnectionStatus.DISCONNECTED);\n      }\n    }\n  }\n\n  /**\n   * Register an event handler for a specific event type\n   */\n  on(eventType: string, callback: EventCallback): () => void {\n    if (!this.eventHandlers.has(eventType)) {\n      this.eventHandlers.set(eventType, new Set());\n\n      // If connected, add the event listener\n      if (this.eventSource && this.eventSource.readyState === EventSource.OPEN) {\n        this.eventSource.addEventListener(eventType, this.handleEvent.bind(this));\n      }\n    }\n\n    const handlers = this.eventHandlers.get(eventType)!;\n    handlers.add(callback);\n\n    // Return a function to remove this handler\n    return () => {\n      if (this.eventHandlers.has(eventType)) {\n        const handlers = this.eventHandlers.get(eventType)!;\n        handlers.delete(callback);\n\n        if (handlers.size === 0) {\n          this.eventHandlers.delete(eventType);\n        }\n      }\n    };\n  }\n\n  /**\n   * Remove all event handlers for a specific event type\n   */\n  off(eventType: string): void {\n    this.eventHandlers.delete(eventType);\n  }\n\n  /**\n   * Attempt to reconnect with exponential backoff\n   */\n  private attemptReconnect(): void {\n    if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n      console.error(`SSE maximum reconnection attempts (${this.maxReconnectAttempts}) reached`);\n      this.setStatus(SSEConnectionStatus.ERROR);\n      return;\n    }\n\n    this.reconnectAttempts++;\n    this.setStatus(SSEConnectionStatus.RECONNECTING);\n\n    // Calculate backoff time (with jitter)\n    const baseDelay = this.reconnectTimeout;\n    const exponentialDelay = baseDelay * Math.pow(1.5, this.reconnectAttempts - 1);\n    const jitter = 0.2 * exponentialDelay;\n    const delay = exponentialDelay + (Math.random() * jitter);\n\n    console.log(`SSE reconnecting in ${Math.round(delay)}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);\n\n    setTimeout(() => {\n      this.connect();\n    }, delay);\n  }\n\n  /**\n   * Start heartbeat to detect connection issues\n   */\n  private startHeartbeat(): void {\n    if (this.heartbeatTimer) {\n      clearInterval(this.heartbeatTimer);\n    }\n\n    this.heartbeatTimer = setInterval(() => {\n      if (this.eventSource && this.eventSource.readyState === EventSource.OPEN) {\n        // Dispatch a heartbeat check event\n        this.dispatchEvent('heartbeat', new MessageEvent('heartbeat', {\n          data: JSON.stringify({\n            timestamp: Date.now()\n          })\n        }));\n      } else if (this.eventSource && this.eventSource.readyState === EventSource.CLOSED) {\n        // Connection is unexpectedly closed\n        console.warn('SSE connection unexpectedly closed during heartbeat check');\n        this.disconnect();\n        if (this.autoReconnect) {\n          this.attemptReconnect();\n        }\n      }\n    }, this.heartbeatInterval);\n  }\n\n  /**\n   * Handle SSE connection open\n   */\n  private handleOpen(event: Event): void {\n    // Clear connection timeout\n    if (this.connectionTimer) {\n      clearTimeout(this.connectionTimer);\n      this.connectionTimer = null;\n    }\n\n    console.log('SSE connection opened');\n    this.setStatus(SSEConnectionStatus.CONNECTED);\n    this.reconnectAttempts = 0;\n\n    // Start heartbeat\n    this.startHeartbeat();\n\n    // Dispatch the open event\n    this.dispatchEvent('open', new MessageEvent('open'));\n  }\n\n  /**\n   * Handle SSE connection error\n   */\n  private handleError(event: Event): void {\n    console.error('SSE connection error:', event);\n\n    // Dispatch the error event\n    this.dispatchEvent('error', new MessageEvent('error'));\n\n    // Set error status\n    this.setStatus(SSEConnectionStatus.ERROR);\n\n    // Attempt to reconnect\n    if (this.autoReconnect) {\n      this.disconnect();\n      this.attemptReconnect();\n    }\n  }\n\n  /**\n   * Handle connection initialization error\n   */\n  private handleConnectionError(error: unknown): void {\n    // Clear connection timeout\n    if (this.connectionTimer) {\n      clearTimeout(this.connectionTimer);\n      this.connectionTimer = null;\n    }\n\n    // Set error status\n    this.setStatus(SSEConnectionStatus.ERROR);\n\n    // Dispatch the error event\n    this.dispatchEvent('error', new MessageEvent('error', {\n      data: error instanceof Error ? error.message : String(error)\n    }));\n\n    // Attempt to reconnect\n    if (this.autoReconnect) {\n      this.attemptReconnect();\n    }\n  }\n\n  /**\n   * Handle generic message event\n   */\n  private handleMessage(event: MessageEvent): void {\n    // Store the last event ID for resuming\n    if (event.lastEventId) {\n      this.lastEventId = event.lastEventId;\n    }\n\n    // Parse the data if it's JSON\n    try {\n      const data = JSON.parse(event.data);\n\n      // If the data has a type field, dispatch to that specific event type\n      if (data.type && typeof data.type === 'string') {\n        this.dispatchEvent(data.type, new MessageEvent(data.type, {\n          data: data.payload || data,\n          lastEventId: event.lastEventId\n        }));\n      }\n    } catch (e) {\n      // Not JSON, just pass through the raw data\n    }\n\n    // Reset reconnect attempts on successful message\n    this.reconnectAttempts = 0;\n\n    // Dispatch to the generic message handler\n    this.dispatchEvent('message', event);\n  }\n\n  /**\n   * Handle specific event type\n   */\n  private handleEvent(event: MessageEvent): void {\n    // Dispatch to handlers for this event type\n    this.dispatchEvent(event.type, event);\n  }\n\n  /**\n   * Dispatch an event to all registered handlers\n   */\n  private dispatchEvent(eventType: string, event: MessageEvent): void {\n    if (this.eventHandlers.has(eventType)) {\n      const handlers = this.eventHandlers.get(eventType)!;\n      for (const handler of handlers) {\n        try {\n          handler(event);\n        } catch (error) {\n          console.error(`Error in SSE event handler for ${eventType}:`, error);\n        }\n      }\n    }\n  }\n}\n\n/**\n * Create a new SSE client\n */\nexport function createSSEClient(options: SSEOptions): MCPSSEClient {\n  return new MCPSSEClient(options);\n}\n"]}