239 lines
6.7 KiB
TypeScript
239 lines
6.7 KiB
TypeScript
import { sessionGet } from '@/utils/cache-session-utils';
|
|
import { getToken } from './auth-token';
|
|
import { localGet } from '@/utils/cache-local-utils';
|
|
import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants';
|
|
import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants';
|
|
|
|
/**连接参数类型 */
|
|
export type OptionsType = {
|
|
/**WebSocket服务地址 */
|
|
url: string;
|
|
/**地址栏参数 */
|
|
params?: Record<string, string | number | boolean | undefined>;
|
|
/**onopen事件的回调函数 */
|
|
onopen?: Function;
|
|
/**message事件的回调函数 */
|
|
onmessage: (data: Record<string, any>) => void;
|
|
/**error事件的回调函数 */
|
|
onerror: Function;
|
|
/**close事件的回调函数 */
|
|
onclose?: (code: number) => void;
|
|
/**心跳周期 若为0则不启用 */
|
|
heartTimer?: number;
|
|
/**重连等待 若为0则不启用 */
|
|
reconnectTimer?: number;
|
|
};
|
|
|
|
/**
|
|
* WebSocket 使用方法
|
|
*
|
|
* import { OptionsType, WS } from '@/plugins/ws-websocket';
|
|
*
|
|
* const ws = new WS();
|
|
*
|
|
* 创建连接
|
|
* const options: OptionsType = { };
|
|
* ws.connect(options);
|
|
*
|
|
* 手动关闭
|
|
* ws.close();
|
|
*/
|
|
export class WS {
|
|
/**ws 实例 */
|
|
private ws: WebSocket | null = null;
|
|
/**ws 连接参数 */
|
|
private options: OptionsType | null = null;
|
|
/**心跳调度器 */
|
|
private heartInterval: number = 0;
|
|
/**重连定时器 */
|
|
private reconnectTimeout: number = 0;
|
|
|
|
/**
|
|
* 构造函数
|
|
* @param {object} params 构造函数参数
|
|
*/
|
|
constructor(options?: OptionsType) {
|
|
if (!window.WebSocket) {
|
|
// 检测浏览器支持
|
|
console.error('Sorry! Browser does not support websocket');
|
|
return;
|
|
}
|
|
options && this.connect(options);
|
|
}
|
|
|
|
/**
|
|
* 创建链接
|
|
* @param {object} options 连接参数
|
|
*/
|
|
public connect(options: OptionsType) {
|
|
this.options = options;
|
|
try {
|
|
if (!options.url.startsWith('ws')) {
|
|
const uri = options.url.startsWith('/')
|
|
? options.url
|
|
: `/${options.url}`;
|
|
// 兼容旧前端可改配置文件
|
|
const wsUrl = import.meta.env.PROD
|
|
? sessionGet('wsUrl') || import.meta.env.VITE_API_BASE_URL
|
|
: '/socket';
|
|
if (wsUrl.startsWith('ws')) {
|
|
options.url = `${wsUrl}${uri}`;
|
|
} else if (wsUrl.startsWith('https')) {
|
|
options.url = `${wsUrl.replace('https', 'wss')}${uri}`;
|
|
} else if (wsUrl.startsWith('http')) {
|
|
options.url = `${wsUrl.replace('http', 'ws')}${uri}`;
|
|
} else {
|
|
const protocol =
|
|
window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
|
options.url = `${protocol}${location.host}${wsUrl}${uri}`;
|
|
}
|
|
}
|
|
|
|
// 地址栏参数
|
|
let params = Object.assign({}, options.params, {
|
|
// 设置 token
|
|
[TOKEN_RESPONSE_FIELD]: getToken(),
|
|
// 多语言
|
|
['language']: localGet(CACHE_LOCAL_I18N) || 'en_US',
|
|
});
|
|
let paramStr = '';
|
|
for (const key in params) {
|
|
const value = params[key];
|
|
// 空字符或未定义的值不作为参数发送
|
|
if (value === '' || value === undefined) continue;
|
|
paramStr += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
|
}
|
|
if (paramStr && paramStr.startsWith('&')) {
|
|
options.url = `${options.url}?${paramStr.substring(1)}`;
|
|
}
|
|
|
|
const ws = new WebSocket(options.url, 'omc-ws');
|
|
// 用于指定连接成功后的回调函数。
|
|
ws.onopen = ev => {
|
|
if (options.heartTimer && options.heartTimer > 0) {
|
|
this.heartCheck(options.heartTimer);
|
|
}
|
|
if (typeof options.onopen === 'function') {
|
|
options.onopen(ev);
|
|
}
|
|
};
|
|
// 用于指定当从服务器接受到信息时的回调函数。
|
|
ws.onmessage = ev => {
|
|
if (ev.type !== 'message') return;
|
|
// 解析文本消息
|
|
try {
|
|
const jsonData = JSON.parse(ev.data);
|
|
if (typeof options.onmessage === 'function') {
|
|
options.onmessage(jsonData);
|
|
}
|
|
} catch (error) {
|
|
console.error('websocket message formatting error', error);
|
|
}
|
|
};
|
|
// 用于指定连接关闭后的回调函数。
|
|
ws.onclose = ev => {
|
|
if (typeof options.onclose === 'function') {
|
|
options.onclose(ev.code);
|
|
}
|
|
};
|
|
// 用于指定连接失败后的回调函数。
|
|
ws.onerror = ev => {
|
|
console.error('websocket connection anomaly', ev);
|
|
|
|
if (typeof options.onerror === 'function') {
|
|
options.onerror(ev);
|
|
}
|
|
if (options.reconnectTimer && options.reconnectTimer > 0) {
|
|
this.reconnect(options.reconnectTimer);
|
|
}
|
|
};
|
|
this.ws = ws;
|
|
} catch (error) {
|
|
if (typeof options.onerror === 'function') {
|
|
options.onerror(error);
|
|
}
|
|
if (options.reconnectTimer && options.reconnectTimer > 0) {
|
|
this.reconnect(options.reconnectTimer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 发送消息
|
|
* @param data JSON数据
|
|
* @returns
|
|
*/
|
|
public send(data: Record<string, any>): boolean {
|
|
if (!this.ws) {
|
|
console.warn('websocket unavailable');
|
|
return false;
|
|
}
|
|
|
|
// 非正常状态关闭
|
|
if (
|
|
this.ws.readyState === WebSocket.CLOSED ||
|
|
this.ws.readyState === WebSocket.CLOSING
|
|
) {
|
|
this.close();
|
|
return false;
|
|
}
|
|
// 正在连接时
|
|
if (this.ws.readyState === WebSocket.CONNECTING) {
|
|
setTimeout(() => {
|
|
this.ws && this.ws.send(JSON.stringify(data));
|
|
}, 1000);
|
|
}
|
|
// 在连接的
|
|
if (this.ws.readyState === WebSocket.OPEN) {
|
|
this.ws.send(JSON.stringify(data));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**连接状态
|
|
*
|
|
* WebSocket.OPEN
|
|
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState)
|
|
*/
|
|
public state(): number {
|
|
if (!this.ws) {
|
|
return -1;
|
|
}
|
|
return this.ws.readyState;
|
|
}
|
|
|
|
// 手动关闭socket
|
|
public close() {
|
|
this.heartInterval && clearInterval(this.heartInterval);
|
|
this.reconnectTimeout && clearTimeout(this.reconnectTimeout);
|
|
if (!this.ws) {
|
|
console.warn('websocket unavailable');
|
|
return;
|
|
}
|
|
this.ws.close(1000, 'user initiated closure');
|
|
}
|
|
|
|
// 周期性发送ping 保活
|
|
private heartCheck(heartTimer: number) {
|
|
this.heartInterval = window.setInterval(() => {
|
|
this.send({
|
|
requestId: `${Date.now()}`,
|
|
type: 'PING',
|
|
});
|
|
}, heartTimer);
|
|
}
|
|
|
|
// 断线重连
|
|
private reconnect(reconnectTimer: number) {
|
|
if (this.reconnectTimeout > 0) return;
|
|
clearTimeout(this.reconnectTimeout);
|
|
this.reconnectTimeout = window.setTimeout(() => {
|
|
if (this.options) {
|
|
this.connect(this.options);
|
|
this.reconnectTimeout = 0;
|
|
}
|
|
}, reconnectTimer);
|
|
}
|
|
}
|