UNPKG

awesome-im

Version:

Lightweight, extensible, JavaScript Instant Messaging.

310 lines (274 loc) 10.7 kB
import { eventEmitter } from '../utils/eventEmitter'; import event from './event'; import { ConnectionStatus, ErrorCode } from './errorCode'; import { PingMessage, SignalMessage, MessageDirection, MessageType } from "./message" import { encode, decode } from '@msgpack/msgpack'; import { guid } from "../utils/util"; export default class WebSocketChannel { constructor({ options }) { const { pingGap, userId, timeout } = options; this._messageIds = {}; this._idCount = 0; this.connectedTime = null; this._timer = null; this._pingTimer = null; this.pingGap = pingGap || 5000; this._status = null; this.userId = userId || null; this.timeout = timeout || 5000; this._url = null; this._socket = null; // eventEmitter.on(event.STATUS, (evt) => { // console.log("init->", evt) // }) } connect(url) { this._url = url; return new Promise((resolve, reject) => { if(this._socket && this._socket.readyState === 1) { return resolve({ code: ErrorCode.CONNECTION_EXIST.code, errMsg: ErrorCode.CONNECTION_EXIST.errMsg }) } const loopConnect = async () => { const resp = await this._connect(url); if (resp.code === ConnectionStatus.CONNECTED) { resolve(resp); this.connectedTime = new Date().getTime(); this._pingTimer = setInterval(async () => { const pingResp = await this._sendPing(); if (pingResp.code !== ErrorCode.SUCCESS.code) { clearInterval(this._pingTimer) loopConnect(); return } }, this.pingGap); } else { resolve(resp) setTimeout(() => { loopConnect() }, this.pingGap); } } loopConnect() }) } disconnect() { if (this._socket) { const messageId = this._generateMessageId(); const data = new SignalMessage({ messageId, from: this.userId, messageUId: guid(), signalName: "disconnect" }); this._send(data); this._socket.close(); this._socket = null; clearInterval(this._timer) clearInterval(this._pingTimer) this._timer = null; this._pingTimer = null; return { code: ErrorCode.SUCCESS.code, errMsg: ErrorCode.SUCCESS.errMsg } } return { code: ErrorCode.NOT_CONNECTED.code, errMsg: ErrorCode.NOT_CONNECTED.errMsg } } reconnect() { return this.connect(this._url); } _send(data) { this._socket?.send(encode(data)) } async _sendPing() { if (this._timer) { clearTimeout(this._timer); } if (!this._socket) return { code: ConnectionStatus.WEBSOCKET_ERROR } const messageId = this._generateMessageId(); const data = new PingMessage({ messageId, from: this.userId, messageUId: guid() }); this._send(data); const pingResp = new Promise((resolve) => { this._messageIds[messageId] = resolve; setTimeout(() => { if (this._messageIds[messageId]) { delete this._messageIds[messageId]; } resolve({ code: ConnectionStatus.TIMEOUT }); // 无值认为 timeout 超时 }, this.pingGap ); }); return pingResp } async _connect(url) { let socket = new WebSocket(url); socket.binaryType = "arraybuffer" this._socket = socket; this.sendConnectTime = Date.now(); const disconnected = () => { if (this._timer) { clearTimeout(this._timer); this._timer = null; } if (this._socket === socket) { this._socket = null; } }; const resp = await new Promise((resolve, reject) => { eventEmitter.emit(event.STATUS, { code: ConnectionStatus.CONNECTING }) socket.onopen = async () => { // 处理连接问题(如果服务不让连接) // const resp = await this._sendData(new SignalMessage({ // signalName: "connect", // })); // 分局服务返回数据判断 // if(resp) { // resolve({ code: ConnectionStatus.WEBSOCKET_UNAVAILABLE }) // eventEmitter.emit(event.STATUS, { code: ConnectionStatus.WEBSOCKET_UNAVAILABLE }); // socket.close() // return false // } resolve({ code: ConnectionStatus.CONNECTED }) eventEmitter.emit(event.STATUS, { code: ConnectionStatus.CONNECTED }); } socket.onmessage = (evt) => { const signal = decode(evt.data) if (Object.prototype.toString.call(evt.data) !== '[object ArrayBuffer]') { this._receiveData({ code: ErrorCode.SERVER_SEND_DATA_NOT_RIGHT.code, errMsg: ErrorCode.SERVER_SEND_DATA_NOT_RIGHT.errMsg }); } else { this._receiveData(signal); } } socket.onclose = () => { disconnected("close"); clearTimeout(this._timer) eventEmitter.emit(event.STATUS, { code: ConnectionStatus.CONNECTION_CLOSED }) resolve({ code: ConnectionStatus.CONNECTION_CLOSED }) } socket.onerror = () => { disconnected("error") clearTimeout(this._timer) eventEmitter.emit(event.STATUS, { code: ConnectionStatus.WEBSOCKET_ERROR }) resolve({ code: ConnectionStatus.WEBSOCKET_ERROR }) } setTimeout(() => { resolve({ code: ConnectionStatus.TIMEOUT, errMsg: "time out" }) }, this.timeout) }) return resp; } close() { if (this._socket) { this._socket.close(); this._socket = null; } } _receiveData(signal) { const { data, code, errMsg } = signal; // 如果不是返回的成功,则返回原信息 if (code !== ErrorCode.SUCCESS.code) { eventEmitter.emit(event.MESSAGE, { code: code, errMsg: errMsg || "receive data error", data }) return false }; signal.data = { ...signal.data, receivedTime: new Date().getTime() } const isHave = data?.hasOwnProperty("messageId") || false; if (!isHave) { signal.data.messageDirection = MessageDirection.RECEIVE // 过滤ping消息通知应用层 data.messageType !== MessageType.PING && eventEmitter.emit(event.MESSAGE, { ...signal, }) return false; } else { if (!this._messageIds[data.messageId]) { signal.data.messageDirection = MessageDirection.RECEIVE // 过滤ping消息通知应用层 data.messageType !== MessageType.PING && eventEmitter.emit(event.MESSAGE, { ...signal, }) return false } } const { messageId, messageType } = data; const resolve = this._messageIds[messageId]; if (resolve) { delete this._messageIds[messageId]; if (messageType === MessageType.PING) { resolve({ code: ErrorCode.SUCCESS.code }); return false } resolve(signal); } else { setTimeout(() => { resolve({ code: ConnectionStatus.TIMEOUT }); }, this.timeout) } } async _sendData(data) { if (!this._socket || this._socket?.readyState !== 1) return { code: 1001, errMsg: "not connected" }; const messageId = this._generateMessageId(); data.messageId = messageId; data.messageUId = guid(); data.from = this.userId; this._send(data); const respSignal = await new Promise((resolve) => { this._messageIds[messageId] = resolve; setTimeout(() => { if (this._messageIds[messageId]) { delete this._messageIds[messageId]; } resolve({ code: ErrorCode.TIMEOUT.code, errMsg: ErrorCode.TIMEOUT.errMsg }); // 无值认为 timeout 超时 }, this.timeout); }); // TODO // 根据服务返回的状态信息,处理数据(当然也可以直接暴露到应用层) if (!respSignal) { return { code: ConnectionStatus.WEBSOCKET_ERROR, errMsg: "websocket error" }; } const isHave = respSignal?.data?.hasOwnProperty("messageId") || false; if (!isHave) { eventEmitter.emit(event.MESSAGE, { code: respSignal.code || ErrorCode.SUCCESS.code, errMsg: respSignal.errMsg || ErrorCode.SUCCESS.errMsg, data: { ...respSignal.data, messageDirection: MessageDirection.RECEIVE } }) } const message = { code: respSignal.code || ErrorCode.SUCCESS.code, errMsg: respSignal.errMsg || ErrorCode.SUCCESS.errMsg, data: { ...respSignal.data, } } return message } _generateMessageId = () => { if (this._idCount >= 65535) { this._idCount = 0; } return ++this._idCount; } }