基于TypeScript的SSE客户端封装库

基于TypeScript的SSE客户端封装库实现,包含了心跳检测、自动重连、错误处理和事件管理等功能:

type SSEEventHandler = (event: MessageEvent) => void;
type SSEErrorHandler = (error: ErrorEvent) => void;

interface SSEOptions {
  url: string;
  headers?: Record<string, string>;
  queryParams?: Record<string, string>;
  heartbeatInterval?: number; // 心跳间隔(ms),默认15000ms
  timeoutThreshold?: number;  // 超时阈值(ms),默认30000ms
  retryInterval?: number;     // 初始重连间隔(ms),默认2000ms
  maxRetryInterval?: number;  // 最大重连间隔(ms),默认30000ms
  backoffFactor?: number;     // 退避因子,默认2
  maxRetries?: number;        // 最大重试次数,默认Infinity
}

export class EventSourceClient {
  private eventSource?: EventSource;
  private options: SSEOptions;
  private eventHandlers: Map<string, SSEEventHandler[]> = new Map();
  private errorHandlers: SSEErrorHandler[] = [];
  private retryCount = 0;
  private retryTimer?: NodeJS.Timeout;
  private heartbeatTimer?: NodeJS.Timeout;
  private lastHeartbeat: number = Date.now();
  private isConnected: boolean = false;

  constructor(options: SSEOptions) {
    this.options = {
      heartbeatInterval: 15000,
      timeoutThreshold: 30000,
      retryInterval: 2000,
      maxRetryInterval: 30000,
      backoffFactor: 2,
      maxRetries: Infinity,
      ...options
    };
  }

  // 连接到SSE服务器
  connect(): void {
    if (this.eventSource) {
      this.close();
    }

    const url = this.buildUrl();
    console.log(`Connecting to SSE: ${url}`);

    try {
      this.eventSource = new EventSource(url);
      
      // 监听原生事件
      this.eventSource.onopen = () => this.handleOpen();
      this.eventSource.onmessage = (event) => this.handleMessage(event);
      this.eventSource.onerror = (error) => this.handleError(error);
      
      // 重置重试计数
      this.retryCount = 0;
    } catch (error) {
      console.error('Failed to create EventSource:', error);
      this.scheduleReconnect();
    }
  }

  // 关闭连接
  close(): void {
    this.clearTimers();
    
    if (this.eventSource) {
      this.eventSource.close();
      this.eventSource = undefined;
    }
    
    this.isConnected = false;
    console.log('SSE connection closed');
  }

  // 添加事件监听器
  on(eventName: string, handler: SSEEventHandler): void {
    if (!this.eventHandlers.has(eventName)) {
      this.eventHandlers.set(eventName, []);
    }
    this.eventHandlers.get(eventName)?.push(handler);
  }

  // 移除事件监听器
  off(eventName: string, handler?: SSEEventHandler): void {
    if (!this.eventHandlers.has(eventName)) return;
    
    if (handler) {
      const handlers = this.eventHandlers.get(eventName) || [];
      this.eventHandlers.set(
        eventName,
        handlers.filter(h => h !== handler)
      );
    } else {
      this.eventHandlers.delete(eventName);
    }
  }

  // 添加错误监听器
  onError(handler: SSEErrorHandler): void {
    this.errorHandlers.push(handler);
  }

  // 移除错误监听器
  offError(handler: SSEErrorHandler): void {
    this.errorHandlers = this.errorHandlers.filter(h => h !== handler);
  }

  // 获取连接状态
  get connectionStatus(): {
    isConnected: boolean;
    retryCount: number;
    lastHeartbeat: number;
  } {
    return {
      isConnected: this.isConnected,
      retryCount: this.retryCount,
      lastHeartbeat: this.lastHeartbeat
    };
  }

  // 构建请求URL(包含查询参数)
  private buildUrl(): string {
    const { url, queryParams } = this.options;
    if (!queryParams || Object.keys(queryParams).length === 0) {
      return url;
    }
    
    const params = new URLSearchParams();
    Object.entries(queryParams).forEach(([key, value]) => {
      params.append(key, value);
    });
    
    return `${url}?${params.toString()}`;
  }

  // 处理连接建立
  private handleOpen(): void {
    this.isConnected = true;
    this.lastHeartbeat = Date.now();
    this.startHeartbeatCheck();
    
    console.log('SSE connection established');
    this.emit('open', { type: 'open' } as MessageEvent);
  }

  // 处理接收到的消息
  private handleMessage(event: MessageEvent): void {
    this.lastHeartbeat = Date.now();
    
    // 处理心跳消息(如果服务端使用特定格式)
    if (event.data.trim() === ': heartbeat') {
      console.debug('Heartbeat received');
      return;
    }
    
    // 处理普通消息
    const eventName = event.type === 'message' ? 'message' : event.type;
    this.emit(eventName, event);
  }

  // 处理错误
  private handleError(error: ErrorEvent): void {
    console.error('SSE error:', error);
    this.isConnected = false;
    
    // 触发错误事件
    this.errorHandlers.forEach(handler => handler(error));
    
    // 关闭当前连接并计划重连
    this.close();
    this.scheduleReconnect();
  }

  // 触发事件
  private emit(eventName: string, event: MessageEvent): void {
    const handlers = this.eventHandlers.get(eventName) || [];
    handlers.forEach(handler => handler(event));
  }

  // 启动心跳检测
  private startHeartbeatCheck(): void {
    this.clearTimers();
    
    this.heartbeatTimer = setInterval(() => {
      const now = Date.now();
      const elapsed = now - this.lastHeartbeat;
      
      if (elapsed > this.options.timeoutThreshold!) {
        console.warn(`Heartbeat timeout: ${elapsed}ms`);
        this.handleError({
          type: 'error',
          message: 'Heartbeat timeout',
          error: new Error('Heartbeat timeout'),
          currentTarget: null,
          target: null
        } as ErrorEvent);
      }
    }, 5000); // 每5秒检查一次
  }

  // 安排重连(带指数退避)
  private scheduleReconnect(): void {
    if (this.retryCount >= this.options.maxRetries!) {
      console.error('Max retries exceeded. Stopping reconnection attempts.');
      this.emit('max-retries-exceeded', { type: 'max-retries-exceeded' } as MessageEvent);
      return;
    }
    
    // 计算退避时间:base * factor^retryCount
    const delay = Math.min(
      this.options.retryInterval! * Math.pow(this.options.backoffFactor!, this.retryCount),
      this.options.maxRetryInterval!
    );
    
    console.log(`Scheduling reconnection attempt ${this.retryCount + 1} in ${delay}ms`);
    
    this.retryTimer = setTimeout(() => {
      this.retryCount++;
      this.connect();
    }, delay);
  }

  // 清除所有定时器
  private clearTimers(): void {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = undefined;
    }
    
    if (this.retryTimer) {
      clearTimeout(this.retryTimer);
      this.retryTimer = undefined;
    }
  }
}

使用示例

// 创建SSE客户端实例
const sseClient = new EventSourceClient({
  url: 'https://api.example.com/stream',
  queryParams: {
    token: 'your-auth-token'
  },
  heartbeatInterval: 15000,
  timeoutThreshold: 30000
});

// 监听消息事件
sseClient.on('message', (event) => {
  console.log('Received message:', event.data);
});

// 监听自定义事件
sseClient.on('notification', (event) => {
  console.log('New notification:', JSON.parse(event.data));
});

// 监听错误
sseClient.onError((error) => {
  console.error('SSE error:', error);
});

// 监听连接状态
sseClient.on('open', () => {
  console.log('SSE connection opened');
});

// 启动连接
sseClient.connect();

// 一段时间后关闭连接
setTimeout(() => {
  sseClient.close();
}, 3600000); // 1小时后关闭

主要功能特点

  1. 自动重连机制

    • 指数退避算法,避免频繁重试
    • 可配置最大重试次数和间隔时间
    • 自动恢复连接状态
  2. 心跳检测

    • 定时检查连接活性
    • 超时自动触发重连
    • 与服务端心跳消息配合
  3. 事件管理

    • 支持自定义事件类型
    • 多监听器管理
    • 事件注册/注销接口
  4. 错误处理

    • 错误事件统一处理
    • 连接状态管理
    • 错误重试机制
  5. 配置灵活性

    • 可配置心跳间隔和超时阈值
    • 支持自定义请求头和查询参数
    • 可定制重连策略

这个封装库可以直接在浏览器环境中使用,也可以在Node.js环境中配合eventsource包使用。它提供了健壮的SSE连接管理,能够处理网络波动、服务端重启等各种异常情况,确保消息的可靠接收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值