WebSocket

/client/src/socket.ts (3.0 KB)

  1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
let appUnloading = false;

export type Message = {
readonly channel: string;
readonly cmd: string;
readonly param: unknown;
}

function socketUrl(u?: string) {
if (!u) {
u = "/connect";
}
if (u.indexOf("ws") === 0) {
return u;
}
const l = document.location;
let protocol = "ws";
if (l.protocol === "https:") {
protocol = "wss";
}
if (u.indexOf("/") !== 0) {
u = "/" + u;
}
return protocol + `://${l.host}${u}`;
}

export class Socket {
readonly debug: boolean;
private readonly open: () => void;
private readonly recv: (m: Message) => void;
private readonly err: (svc: string, err: string) => void;
readonly url?: string;
connected: boolean;
pauseSeconds: number;
pendingMessages: Message[];
connectTime?: number;
sock?: WebSocket;

constructor(debug: boolean, o: () => void, r: (m: Message) => void, e: (svc: string, err: string) => void, url?: string) {
this.debug = debug;
this.open = o;
this.recv = r;
this.err = e;
this.url = socketUrl(url);
this.connected = false;
this.pauseSeconds = 1;
this.pendingMessages = [];

this.connect();
}

connect() {
window.onbeforeunload = () => {
appUnloading = true;
};
this.connectTime = Date.now();
this.sock = new WebSocket(socketUrl(this.url));
const s = this; // eslint-disable-line @typescript-eslint/no-this-alias
this.sock.onopen = () => {
s.connected = true;
s.pendingMessages.forEach(s.send);
s.pendingMessages = [];
if (s.debug) {
console.log("WebSocket connected");
}
s.open();
};
this.sock.onmessage = (event) => {
const msg = JSON.parse(event.data) as Message;
if (s.debug) {
console.debug("[socket]: receive", msg);
}
s.recv(msg);
};
this.sock.onerror = (event) => () => {
s.err("socket", event.type);
};
this.sock.onclose = () => {
if (appUnloading) {
return;
}
s.connected = false;
const elapsed = s.connectTime ? Date.now() - s.connectTime : 0;
if (elapsed > 0 && elapsed < 2000) {
s.pauseSeconds = s.pauseSeconds * 2;
if (s.debug) {
console.debug(`socket closed immediately, reconnecting in ${s.pauseSeconds} seconds`);
}
setTimeout(() => {
s.connect();
}, s.pauseSeconds * 1000);
} else {
console.debug("socket closed after [" + elapsed + "ms]");
setTimeout(() => {
s.connect();
}, s.pauseSeconds * 500);
}
};
}

disconnect() {
// noop
}

send(msg: Message) {
if (this.debug) {
console.debug("out", msg);
}
if (!this.sock) {
throw new Error("socket not initialized");
}
if (this.connected) {
const m = JSON.stringify(msg, null, 2);
this.sock.send(m);
} else {
this.pendingMessages.push(msg);
}
}
}

export function socketInit() {
return Socket;
}