typescript封装websocket,监测心跳,断开重连

6 篇文章 0 订阅
4 篇文章 0 订阅
interface Class2Type {
  [key: string]: string;
}

export function _typeof(value: any) {
  const class2type: Class2Type = {};
  "Boolean Number String Function Array Date RegExp Object Error".split(" ").forEach((key) => {
    class2type["[object " + key + "]"] = key.toLowerCase();
  });

  if (value === null) return String(value);

  return typeof value === "object" || typeof value === "function" ? class2type[class2type.toString.call(value)] || "object" : typeof value;
}

/**
 * websocket 类
 * @class
 * @param {object|string} options-传递参数
 * @param {string} options.url-websocket 连接地址
 * @param {string|object[]} [options.protocol]-子协议
 * @param {reconnect} [options.reconnect]-断开后是否自动连接
 * @param {number} [reconnectIntervalInMilliSeconds]-连接时间
 **/

interface Options {
  url: string;
  reconnect?: boolean;
  reconnectIntervalInMilliSeconds?: number;
}

interface Watcher {
  open: any[];
  message: any[];
  close: any[];
  error: any[];
}

type WebSocketPayload = string | ArrayBuffer | Blob

interface WebSocketLike {
  close(): any;
  send(data: WebSocketPayload): any;
  onopen: ((event: any) => any) | null;
  onclose: ((event: any) => any) | null;
  onmessage: ((event: any) => any) | null;
  onerror: ((event: any) => any) | null;
  readyState: number;
}

const defaultOptions = {
  url: "",
  reconnect: true,
  reconnectIntervalInMilliSeconds: 0,
};

class WebSocketClass {
  private ws: WebSocketLike | null;
  private reconnectTimeOut: null | number;
  private readonly url: string;
  private readonly reconnect: boolean;
  private readonly reconnectIntervalInMilliSeconds: number;
  private isDestroy: boolean;
  private attempts: number;
  private serverHeartTimeout: null | number;
  private heartTimeout: null | number;
  private heartTime: number;
  private heart: boolean;
  private watcher: Watcher;
  private isReconnected: boolean;

  constructor(options: string | Options) {
    let _options = {};
    if (_typeof(options) === 'string') {
      _options = { url: options };
    } else if (_typeof(options) === 'object' && options !== null) {
      _options = options;
    }

    const option = Object.assign({}, defaultOptions, _options);

    this.ws = new WebSocket(option.url);
    this.url = option.url;
    this.reconnect = option.reconnect;
    this.reconnectIntervalInMilliSeconds = option.reconnectIntervalInMilliSeconds;
    this.attempts = 1;
    this.isDestroy = false;

    this.reconnectTimeOut = null;

    this.heart = false;
    this.heartTime = 40000;
    this.heartTimeout = null;
    this.serverHeartTimeout = null;
    this.isReconnected = false; // 是否在重连中

    this.watcher = {
      open: [],
      message: [],
      error: [],
      close: [],
    };

    this._init();
  }

  // Websocket 开启连接时执行
  onOpen(callback: () => void) {
    this.watcher['open'].push(callback);
  }

  // websocket 在通信中执行函数
  onMessage(callback: () => void) {
    this.watcher['message'].push(callback);
  }

  // websocket 断开连接执行函数
  onClose(callback: () => void) {
    this.watcher['close'].push(callback);
  }

  // 销毁当前websocket
  destroy() {
    this.isDestroy = true;

    if (this.ws) this.ws.close();
    this.ws = null;

    // 清除延时
    clearTimeout(this.heartTimeout!);
    clearTimeout(this.serverHeartTimeout!);
    clearTimeout(this.reconnectTimeOut!);
    this.heartTimeout = null;
    this.serverHeartTimeout = null;
    this.reconnectTimeOut = null;
  }

  // 重连
  doReconnect() {
    if (this.reconnect) {
      // 存在ws 则先关闭
      if (this.ws) {
        this.ws.close();
        this.ws = null;
      }

      // 主动destroy,不进行重连
      if (this.isDestroy) return;

      // 重连判断 在重连中则返回
      if (this.isReconnected) return;
      this.isReconnected = true;

      const time = this._generateInterval(this.attempts);
      this.reconnectTimeOut = setTimeout(() => {
        this.attempts += 1;
        this.isReconnected = false;
        this.ws = new WebSocket(this.url);
        this._init();
      }, time);
    }
  }

  // 初始化
  _init() {
    this.ws!.onopen = () => {
      this.watcher['open'].forEach(fn => fn());
      this.sendHeart();
    };

    this.ws!.onmessage = (event: any) => {
      // 过滤心跳数据
      const data = JSON.parse(event.data) || {};
      if (data.c !== "r" || data.r !== "h") {
        this.watcher['message'].forEach(fn => fn(data));
      }
      this.sendHeart();
    };

    this.ws!.onclose = () => {
      this.watcher['close'].forEach(fn => fn());
      this.doReconnect();
    };

    this.ws!.onerror = () => { // 默认ws报错就执行重连 todo -- 解决第一次连接失败
      this.doReconnect();
    };
  }
  
  // 监听心跳 重连
  sendHeart() {
    const that = this;
    clearTimeout(this.heartTimeout!);
    clearTimeout(this.serverHeartTimeout!);

    if (!this.isDestroy) { // ws 已经删除不做心跳重连
      this.heartTimeout = setTimeout(() => {
        // 发送心跳
        if (this.ws && this.ws.readyState === 1) {
          this.ws.send(JSON.stringify({ 'c': 'h' }));
        }

        that.serverHeartTimeout = setTimeout(() => { // 如果超过一定时间还没重置,说明后端断开了
          that.doReconnect();
        }, that.heartTime);
      }, that.heartTime);
    }
  }

  // websocket 发送消息
  sendMessage(message: object | string) {
    const that = this;
    if (message && this.ws) {
      try {
        this.ws.send(JSON.stringify(message));
      } catch (err) {
        const timer = setInterval(() => {
          if (that.ws && that.ws.readyState === 1) {
            that.ws.send(JSON.stringify(message));
            clearInterval(timer);
          }
        }, 200);

        const timer01 = setTimeout(() => {
          clearTimeout(timer01);
          clearInterval(timer);
        }, 10000);
      }
    }
  }

  // 重连时间间隔控制
  _generateInterval(k: number) {
    if (this.reconnectIntervalInMilliSeconds > 0) {
      return this.reconnectIntervalInMilliSeconds;
    }
    return Math.min(30, (Math.pow(2, k) - 1)) * 1000;
  }
}

const websocket = (options: string | Options) => new WebSocketClass(options);

export {
  websocket,
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值