imsdk-server-core
Version:
轻量级Web服务器框架、WebSocket服务器框架。采用Typescript编写,简单易用。
502 lines (501 loc) • 19.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WssBridge = exports.WssBridgeResponse = exports.WssBridgeRequest = exports.WssBridgeListener = exports.WssBridgePackData = void 0;
/**
* WssServer的客户端
*/
const ws_1 = __importDefault(require("ws"));
const crypto_js_1 = __importDefault(require("crypto-js"));
class WssBridgePackData {
/**
* @param route 路由
* @param reqId 请求序号
* @param message 报文数据
*/
constructor(route, reqId, message) {
this.route = route;
this.reqId = reqId;
this.message = message;
}
/**
* 将数据包进行序列化,采用随机生成iv和key的AES加密算法,CBC、Pkcs7
* @param pack 要序列化的数据包
* @param pwd 加密的密码
* @param binary 是否返回二进制结果,设置了pwd时生效
*/
static serialize(pack, pwd, binary) {
try {
const str = JSON.stringify(pack);
if (pwd) {
//ArrayBuffer or base64 string
const salt = crypto_js_1.default.lib.WordArray.random(16);
const iv = crypto_js_1.default.lib.WordArray.random(16);
const key = crypto_js_1.default.HmacSHA256(salt, pwd);
const body = crypto_js_1.default.AES.encrypt(str, key, {
iv: iv,
mode: crypto_js_1.default.mode.CBC,
padding: crypto_js_1.default.pad.Pkcs7
}).ciphertext;
const encRes = crypto_js_1.default.lib.WordArray.create();
encRes.concat(salt).concat(iv).concat(body);
return binary ? new Int32Array(encRes.words).buffer : encRes.toString(crypto_js_1.default.enc.Base64);
}
else {
//json string
return str;
}
}
catch (e) {
return null;
}
}
/**
* 将收到的数据进行反序列化,采用随机生成iv和key的AES解密算法,CBC、Pkcs7
* @param data 要解密的数据
* @param pwd 解密的密码
*/
static deserialize(data, pwd) {
try {
if (pwd) {
//ArrayBuffer or base64 string
const words = data instanceof ArrayBuffer ? Array.prototype.slice.call(new Int32Array(data)) : crypto_js_1.default.enc.Base64.parse(data).words;
const salt = crypto_js_1.default.lib.WordArray.create(words.slice(0, 4));
const iv = crypto_js_1.default.lib.WordArray.create(words.slice(4, 8));
const key = crypto_js_1.default.HmacSHA256(salt, pwd);
const body = crypto_js_1.default.lib.WordArray.create(words.slice(8));
const decRes = crypto_js_1.default.AES.decrypt({ ciphertext: body }, key, {
iv: iv,
mode: crypto_js_1.default.mode.CBC,
padding: crypto_js_1.default.pad.Pkcs7
}).toString(crypto_js_1.default.enc.Utf8);
const obj = JSON.parse(decRes);
return new WssBridgePackData(obj.route, obj.reqId, obj.message);
}
else {
//json string
const obj = data instanceof ArrayBuffer ? {} : JSON.parse(data);
return new WssBridgePackData(obj.route, obj.reqId, obj.message);
}
}
catch (e) {
return null;
}
}
/**
* 计算md5编码
* @param data 要计算编码的字符串
*/
static getMd5(data) {
return crypto_js_1.default.MD5(data).toString();
}
}
exports.WssBridgePackData = WssBridgePackData;
/**
* 路由
*/
WssBridgePackData.ROUTE_HEARTICK = '$heartick$'; //心跳包路由
WssBridgePackData.ROUTE_RESPONSE = '$response$'; //响应请求路由
/**
* 状态
* 本框架保留状态码:
* 4001-4100 服务端保留状态码范围
* 4101-4200 客户端保留状态码范围
* 4201-4999 可自定义的状态码范围
*/
WssBridgePackData.CODE_RETRY = { code: 4101, data: 'retry' };
WssBridgePackData.CODE_CLOSE = { code: 4102, data: 'close' };
WssBridgePackData.CODE_ERROR = { code: 4103, data: 'error' };
WssBridgePackData.CODE_CALL = { code: 4104, data: 'call' };
class WssBridgeListener {
constructor(once, onmessage, context, params) {
this.once = once;
this.onmessage = onmessage;
this.context = context || this;
this.params = params;
}
callMessage(message) {
if (this.onmessage) {
this.onmessage.call(this.context, message, this.params);
}
}
}
exports.WssBridgeListener = WssBridgeListener;
class WssBridgeRequest {
constructor(onsuccess, onerror, context, params) {
this.time = Date.now();
this.onsuccess = onsuccess;
this.onerror = onerror;
this.context = context || this;
this.params = params;
}
callSuccess(resp) {
if (this.onsuccess) {
this.onsuccess.call(this.context, resp, this.params);
}
}
callError(resp) {
if (this.onerror) {
this.onerror.call(this.context, resp, this.params);
}
}
}
exports.WssBridgeRequest = WssBridgeRequest;
class WssBridgeResponse {
constructor(code, data) {
this.code = code;
this.data = data;
}
get ok() { return this.code === 200; }
}
exports.WssBridgeResponse = WssBridgeResponse;
class WssBridge {
/**
* @param host 服务器地址(http://、https://、ws://、wss://)
* @param pwd 数据加解密密码
* @param binary 是否用二进制传输
* @param timeout 请求超时(毫秒)
* @param heartick 心跳间隔(秒)
* @param conntick 重连间隔(秒)
*/
constructor(host, pwd, binary, timeout = 8000, heartick = 60, conntick = 3) {
this._host = host.indexOf('https:') === 0 ? host.replace('https:', 'wss:') : (host.indexOf('http:') === 0 ? host.replace('http:', 'ws:') : host);
this._pwd = pwd;
this._binary = binary;
this._timeout = timeout;
this._heartick = heartick;
this._conntick = conntick;
this._timer = null;
this._timerInc = 0;
this._reqIdInc = 0;
this._netDelay = 0;
this._retryCnt = 0;
this._listeners = {};
this._requests = {};
this._logLevel = WssBridge.LOG_LEVEL_NONE;
this._socket = null;
this._paused = false;
this._expired = false;
}
onSocketOpen(e) {
if (this._logLevel < WssBridge.LOG_LEVEL_NONE)
console.log('connected', this._host);
this._retryCnt = 0; //重置重连次数为0
if (this._onopen)
this._onopen.call(this._context, this._params);
}
onSocketMessage(e) {
if (this._expired)
return;
this.readPackData(e.data);
}
onSocketClose(e) {
if (this._expired)
return;
this.safeClose(WssBridgePackData.CODE_CLOSE.code, WssBridgePackData.CODE_CLOSE.data);
if (this._onclose)
this._onclose.call(this._context, e.code || 0, e.reason || 'Unknow Reason', this._params);
}
onSocketError(e) {
if (this._expired)
return;
this.safeClose(WssBridgePackData.CODE_ERROR.code, WssBridgePackData.CODE_ERROR.data);
if (this._onerror)
this._onerror.call(this._context, e.message || 'Unknow Error', this._params);
}
onTimerTick() {
//秒数自增
this._timerInc++;
//清除超时的请求
const time = Date.now();
const list = [];
for (let reqId in this._requests) {
const request = this._requests[reqId];
if (time - request.time > this._timeout) {
request.callError(new WssBridgeResponse(504, 'Gateway Timeout'));
list.push(reqId);
}
}
for (let i = 0; i < list.length; i++) {
delete this._requests[list[i]];
}
//心跳和断线重连
if (this.isConnected()) {
if (this._timerInc % this._heartick === 0) {
this.sendPackData(new WssBridgePackData(WssBridgePackData.ROUTE_HEARTICK, this._reqIdInc++, Date.now())); //发送心跳包
}
}
else {
if (this._timerInc % this._conntick === 0 && !this._paused) {
this._retryCnt++; //增加重连次数
if (this._onretry)
this._onretry.call(this._context, this._retryCnt, this._params);
this.safeOpen(); //安全开启连接
}
}
//秒钟回调
if (this._onsecond) {
this._onsecond.call(this._context, this._timerInc, this._netDelay, this._params);
}
}
sendPackData(pack) {
if (this._expired)
return;
if (this.isConnected()) {
const data = WssBridgePackData.serialize(pack, this._pwd, this._binary);
if (!data) {
if (this._onerror)
this._onerror.call(this._context, 'Serialize Error', this._params);
return;
}
this._socket.send(data);
this.printPackData('sendPackData >>>', pack);
}
}
readPackData(data) {
const pack = WssBridgePackData.deserialize(data, this._pwd);
if (!pack) {
if (this._onerror)
this._onerror.call(this._context, 'Deserialize Error', this._params);
return;
}
this.printPackData('readPackData <<<', pack);
switch (pack.route) {
case WssBridgePackData.ROUTE_HEARTICK:
//服务端心跳响应
this._netDelay = Date.now() - pack.message; //更新网络延迟
if (this._logLevel === WssBridge.LOG_LEVEL_ALL)
console.log('net delay:', this._netDelay + 'ms');
break;
case WssBridgePackData.ROUTE_RESPONSE:
//客户端请求响应
const request = this._requests[pack.reqId];
if (!request)
return; //超时的响应,监听器已经被_timer删除
this._netDelay = Date.now() - request.time; //更新网络延迟
if (this._logLevel === WssBridge.LOG_LEVEL_ALL)
console.log('net delay:', this._netDelay + 'ms');
const message = pack.message || {};
const resp = new WssBridgeResponse(message.code, message.data);
if (resp.ok) {
request.callSuccess(resp);
}
else {
request.callError(resp);
}
delete this._requests[pack.reqId];
break;
default:
//服务器主动推送
this.triggerEvent(pack);
break;
}
}
printPackData(title, pack) {
if (pack.route === WssBridgePackData.ROUTE_HEARTICK) {
if (this._logLevel === WssBridge.LOG_LEVEL_ALL) {
console.group(title);
console.log('route:', pack.route);
if (pack.reqId !== undefined)
console.log('reqId:', pack.reqId);
if (pack.message !== undefined)
console.log('message:', pack.message);
console.groupEnd();
}
}
else if (this._logLevel <= WssBridge.LOG_LEVEL_DATA) {
console.group(title);
console.log('route:', pack.route);
if (pack.reqId !== undefined)
console.log('reqId:', pack.reqId);
if (pack.message !== undefined)
console.log('message:', pack.message);
console.groupEnd();
}
}
safeOpen() {
this.safeClose(WssBridgePackData.CODE_RETRY.code, WssBridgePackData.CODE_RETRY.data); //关闭旧连接
if (this._expired)
return;
/**
* 经测试JS版本不管是浏览器还是服务端,CONNECTING与OPEN状态都可以直接调用close函数,最终都会为CLOSED
* 建立新实例之前可以完美的销毁旧实例,所以无需加锁
* 0 CONNECTING - The connection is not yet open.
* 1 OPEN - The connection is open and ready to communicate.
* 2 CLOSING - The connection is in the process of closing.
* 3 CLOSED- The connection is closed.
*/
this._socket = new ws_1.default(this._host, this.isNative() ? { rejectUnauthorized: false } : undefined); //创建WebSocket对象
this._socket.binaryType = 'arraybuffer';
this._socket.onopen = (e) => { this.onSocketOpen(e); }; //添加连接打开侦听,连接成功会调用此方法
this._socket.onmessage = (e) => { this.onSocketMessage(e); }; //添加收到数据侦听,收到数据会调用此方法
this._socket.onclose = (e) => { this.onSocketClose(e); }; //添加连接关闭侦听,手动关闭或者服务器关闭连接会调用此方法
this._socket.onerror = (e) => { this.onSocketError(e); }; //添加异常侦听,出现异常会调用此方法
}
safeClose(code, reason) {
if (this._socket) {
this._socket.close(code, reason);
this._socket = null;
}
}
/**
* 开始进行网络连接
* @param onopen 网络连接建立时的回调
* @param onclose 网络连接关闭时的回调(包括手动关闭、服务端关闭等情况)
* @param onerror 网络连接发生错误时的回调
* @param onretry 网络连接断开,自动重连时的回调
* @param onsecond 此函数每秒回调一次,回调参数中包含网络延迟等信息
* @param context 触发回调函数时的绑定的上下文信息
* @param params 触发回调函数时会传回这个参数
*/
connect(onopen, onclose, onerror, onretry, onsecond, context, params) {
this._onopen = onopen;
this._onclose = onclose;
this._onerror = onerror;
this._onretry = onretry;
this._onsecond = onsecond;
this._context = context || this;
this._params = params;
//打开
this.safeOpen(); //安全开启连接
this._timer = setInterval(() => { this.onTimerTick(); }, 1000);
}
/**
* 强制关闭网络连接,并销毁这个实例
* 注意:调用此函数后,此实例不可继续做网络操作,不可重新连接网络。
*/
disconnect() {
if (this._logLevel < WssBridge.LOG_LEVEL_NONE)
console.log('disconnected', this._host);
this._expired = true;
//关闭
if (this._timer) {
clearInterval(this._timer);
this._timer = null;
}
this.safeClose(WssBridgePackData.CODE_CALL.code, WssBridgePackData.CODE_CALL.data); //安全关闭连接
}
/**
* 向远程服务器发起请求
* @param route 远程服务器路由地址
* @param message 数据包
* @param onsuccess 请求成功的回调
* @param onerror 请求失败的回调
* @param context 触发回调函数时的绑定的上下文信息
* @param params 触发回调函数时会传回这个参数
*/
request(route, message, onsuccess, onerror, context, params) {
const reqId = this._reqIdInc++;
if (onsuccess || onerror)
this._requests[reqId] = new WssBridgeRequest(onsuccess, onerror, context, params); //有监听器的放入请求队列
this.sendPackData(new WssBridgePackData(route, reqId, message));
}
/**
* 添加指定route的监听器,可用作自由定义事件的管理器
* @param route 网络路由名称、本地自定义事件名称
* @param once 是否触发一次后,自动删除此路由
* @param onmessage 触发时的回调
* @param context 触发回调函数时的绑定的上下文信息
* @param params 触发回调函数时会传回这个参数
*/
addListener(route, once, onmessage, context, params) {
let listeners = this._listeners[route];
if (listeners === undefined) {
listeners = [];
this._listeners[route] = listeners;
}
listeners.push(new WssBridgeListener(once, onmessage, context, params));
}
/**
* 删除指定route的监听器
* @param route 网络路由名称、本地自定义事件名称
* @param onmessage 要删除的监听器。不传这个参数则删除route对应的全部路由
*/
removeListener(route, onmessage) {
const listeners = this._listeners[route];
if (!listeners)
return;
if (onmessage === undefined) {
delete this._listeners[route]; //删除该路由的全部监听
}
else {
const list = [];
for (let i = 0; i < listeners.length; i++) {
const item = listeners[i];
if (item.onmessage === onmessage) {
list.push(item);
}
}
while (list.length > 0) {
const index = listeners.indexOf(list.pop());
if (index >= 0) {
listeners.splice(index, 1);
}
}
if (listeners.length === 0) {
delete this._listeners[route];
}
}
}
/**
* 设置监听器的前置解码器,该解码器将在addListener设置的监听器回调之前调用
* * @param listenerDecoder 自定义解码器
*/
setListenerDecoder(listenerDecoder, listenerContext) {
this._listenerDecoder = listenerDecoder;
this._listenerContext = listenerContext || this;
}
/**
* 手动触发pack.route对应的全部监听器
* @param pack 路由包装实例
*/
triggerEvent(pack) {
const listeners = this._listeners[pack.route];
if (!listeners)
return;
const oncelist = []; //删除只触发一次的监听
const message = !this._listenerDecoder ? pack.message : this._listenerDecoder.call(this._listenerContext, pack);
for (let i = 0; i < listeners.length; i++) {
const item = listeners[i];
item.callMessage(message);
if (item.once) {
oncelist.push(item);
}
}
for (let i = 0; i < oncelist.length; i++) {
this.removeListener(pack.route, oncelist[i].onmessage);
}
}
/**
* 暂停断线自动重连的功能
*/
pauseReconnect() { this._paused = true; }
/**
* 恢复断线自动重连的功能
*/
resumeReconnect() { this._paused = false; }
/**
* 设置调试日志输出级别
* @param level 日志级别,有效值为 WssBridge.LOG_LEVEL_XXX
*/
setLogLevel(level) { this._logLevel = level; }
/**
* 获取当前网络延迟毫秒
*/
getNetDelay() { return this._netDelay; }
/**
* 是否已经建立网络连接
*/
isConnected() { return this._socket && this._socket.readyState === ws_1.default.OPEN; }
/**
* 是否为服务端node环境
*/
isNative() { return typeof module === 'object'; }
}
exports.WssBridge = WssBridge;
WssBridge.LOG_LEVEL_ALL = 1;
WssBridge.LOG_LEVEL_DATA = 2;
WssBridge.LOG_LEVEL_INFO = 3;
WssBridge.LOG_LEVEL_NONE = 4;