223 lines
6.0 KiB
TypeScript
223 lines
6.0 KiB
TypeScript
|
|
import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api";
|
|
import {ControlEvent, ControlMsgType} from "@/api";
|
|
import {isDevMode} from "@/composables/buildMode";
|
|
|
|
|
|
interface IWebsocket {
|
|
|
|
close(): void;
|
|
|
|
send(msg: ServerMsg): void;
|
|
}
|
|
|
|
class WebsocketDummy implements IWebsocket {
|
|
close(): void {
|
|
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
send(msg: ServerMsg) {
|
|
|
|
}
|
|
}
|
|
|
|
class OneTimeWebsocket implements IWebsocket {
|
|
private readonly heartBeatTimeout: number = 1;
|
|
private readonly host: string;
|
|
private readonly intervalId: number;
|
|
private readonly msgCallback: (ev: ServerMsg) => any;
|
|
private readonly ctrlCallback: (msg: ControlMsg) => any;
|
|
private readonly closeCallback: () => void;
|
|
private socket: WebSocket;
|
|
private heartBeatTimeCount: number;
|
|
private stoped: boolean;
|
|
private cleared: boolean;
|
|
private hasBeenConnected: boolean;
|
|
|
|
constructor(host: string,
|
|
msgCallback: (ev: ServerMsg) => any,
|
|
ctrlCallback: (msg: ControlMsg) => any,
|
|
closeCallback: () => void
|
|
) {
|
|
this.host = host;
|
|
this.stoped = false;
|
|
this.cleared = false;
|
|
this.hasBeenConnected = false;
|
|
this.msgCallback = msgCallback;
|
|
this.ctrlCallback = ctrlCallback;
|
|
this.closeCallback = closeCallback;
|
|
this.heartBeatTimeCount = 0;
|
|
this.intervalId = 0;
|
|
|
|
this.socket = new WebSocket(`ws://${this.host}/ws`);
|
|
this.setSocketProp();
|
|
|
|
this.intervalId = setInterval(() => {
|
|
if (this.heartBeatTimeCount > this.heartBeatTimeout) {
|
|
/* did not receive packet "heartBeatTimeout" times,
|
|
* connection may be lost: close the socket */
|
|
if (this.socket.readyState === this.socket.OPEN) {
|
|
console.log("No heart beat, break connection");
|
|
this.close();
|
|
this.clear();
|
|
}
|
|
if (isDevMode()) {
|
|
console.log("interval: ", this.heartBeatTimeCount, "state: ", this.socket.readyState);
|
|
}
|
|
}
|
|
|
|
this.heartBeatTimeCount++;
|
|
}, 2000);
|
|
}
|
|
|
|
private setSocketProp() {
|
|
this.socket.binaryType = 'arraybuffer'
|
|
this.heartBeatTimeCount = 0;
|
|
|
|
this.socket.onmessage = (ev) => {
|
|
this.heartBeatTimeCount = 0;
|
|
if (!ev.data)
|
|
return
|
|
|
|
const msg: ServerMsg = {
|
|
data: ev.data,
|
|
type: "json",
|
|
}
|
|
if (typeof ev.data === "string") {
|
|
msg.type = "json"
|
|
} else {
|
|
msg.type = "binary";
|
|
}
|
|
this.msgCallback(msg);
|
|
}
|
|
|
|
this.socket.onclose = (ev) => {
|
|
if (isDevMode()) {
|
|
console.log("ws closed", ev.reason, ev.code);
|
|
}
|
|
this.socket.onclose = null
|
|
this.socket.onopen = null
|
|
this.socket.onerror = null
|
|
this.socket.onmessage = null;
|
|
this.clear();
|
|
};
|
|
|
|
this.socket.onerror = (error) => {
|
|
this.close();
|
|
};
|
|
|
|
this.socket.onopen = ev => {
|
|
// console.log('WebSocket Connected');
|
|
if (this.stoped) {
|
|
this.close();
|
|
return;
|
|
}
|
|
this.heartBeatTimeCount = 0;
|
|
this.hasBeenConnected = true;
|
|
const msg: ControlMsg = {
|
|
type: ControlMsgType.WS_EVENT,
|
|
data: ControlEvent.CONNECTED,
|
|
}
|
|
this.ctrlCallback(msg);
|
|
};
|
|
|
|
const msg: ControlMsg = {
|
|
type: ControlMsgType.WS_EVENT,
|
|
data: ControlEvent.CONNECTING,
|
|
}
|
|
this.ctrlCallback(msg);
|
|
}
|
|
|
|
close() {
|
|
this.stoped = true;
|
|
this.socket.close();
|
|
}
|
|
|
|
send(msg: ServerMsg) {
|
|
if (this.socket.readyState !== WebSocket.OPEN) {
|
|
return;
|
|
}
|
|
if (isDevMode()) {
|
|
console.log('WebSocket proxies data ', msg);
|
|
}
|
|
|
|
this.socket.send(msg.data);
|
|
}
|
|
|
|
clear() {
|
|
if (this.cleared) {
|
|
return;
|
|
}
|
|
this.cleared = true;
|
|
clearInterval(this.intervalId);
|
|
|
|
const msg: ControlMsg = {
|
|
type: ControlMsgType.WS_EVENT,
|
|
data: ControlEvent.DISCONNECTED,
|
|
}
|
|
this.ctrlCallback(msg);
|
|
this.closeCallback();
|
|
}
|
|
}
|
|
|
|
export class WebsocketWrapper {
|
|
private socket: IWebsocket;
|
|
private msgCallback: (ev: ServerMsg) => any;
|
|
private ctrlCallback: (msg: ControlMsg) => any;
|
|
private timeoutId: number;
|
|
private host: string;
|
|
private stoped: boolean;
|
|
|
|
constructor() {
|
|
this.socket = new WebsocketDummy();
|
|
this.msgCallback = () => {};
|
|
this.ctrlCallback = () => {};
|
|
this.timeoutId = 0;
|
|
this.host = "";
|
|
this.stoped = false;
|
|
|
|
this.closeCallback = this.closeCallback.bind(this);
|
|
}
|
|
|
|
init(host: string,
|
|
msgCallback: (ev: ServerMsg) => any,
|
|
ctrlCallback: (msg: ControlMsg) => any)
|
|
{
|
|
if (this.host === host) {
|
|
return;
|
|
}
|
|
this.newConnection(host, msgCallback, ctrlCallback);
|
|
}
|
|
|
|
private newConnection(host: string,
|
|
msgCallback: (ev: ServerMsg) => any,
|
|
ctrlCallback: (msg: ControlMsg) => any)
|
|
{
|
|
this.socket.close();
|
|
this.host = host;
|
|
this.msgCallback = msgCallback;
|
|
this.ctrlCallback = ctrlCallback;
|
|
this.socket = new OneTimeWebsocket(host, this.msgCallback, this.ctrlCallback, this.closeCallback);
|
|
}
|
|
|
|
private closeCallback() {
|
|
if (this.stoped) {
|
|
return;
|
|
}
|
|
this.timeoutId = setTimeout(() =>
|
|
this.newConnection(this.host, this.msgCallback, this.ctrlCallback),
|
|
1000);
|
|
}
|
|
|
|
deinit() {
|
|
this.socket.close();
|
|
this.stoped = true;
|
|
clearTimeout(this.timeoutId);
|
|
}
|
|
|
|
send(msg: ServerMsg) {
|
|
this.socket.send(msg)
|
|
}
|
|
}
|