awesome-im
Version:
Lightweight, extensible, JavaScript Instant Messaging.
310 lines (274 loc) • 10.7 kB
JavaScript
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;
}
}