-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
136 lines (123 loc) · 3.94 KB
/
index.ts
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
124
125
126
127
128
129
130
131
132
133
134
135
136
import { EventEmitter } from "events";
const MAX_RECONNECT_ATTEMPTS = 10;
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export interface WSReqonetOptions {
/** # attempts to reconnect */
maxRetryAttempts?: number;
/** Whether to store 'send' data when connection is broken */
queueMessage?: boolean;
/** whether to disable reconnection */
disableReconnect?: boolean;
/** enable to get console.log output */
debug?: boolean;
}
// websocket with reconnection on exponential back-off
export default class WSReqonet extends EventEmitter {
private ws: WebSocket;
private retryAttempts = 0;
private maxRetryAttempts: number;
private queueMessage: boolean;
private messageQueue: Array<string | Blob | ArrayBuffer | ArrayBufferView> =
[];
private reconnectAttempts = 0;
private intervalRef = 0;
private forcedClose = false;
private disableReconnect: boolean;
constructor(
url: string | URL,
private protocols: string | string[] = [],
options: WSReqonetOptions = {}
) {
super();
this.maxRetryAttempts = options.maxRetryAttempts ?? 10;
this.queueMessage = options.queueMessage ?? true;
this.disableReconnect = options.disableReconnect ?? false;
if (!options?.debug) {
console.log = () => {};
}
this.ws = new window.WebSocket(url, protocols);
this.connect();
}
private onopen = () => {
this.emit("open");
this.forcedClose = false;
};
private onmessage = (event: MessageEvent<any>) => {
this.emit("message", event);
};
private onError = (error: Event) => {
this.emit("error", error);
this.reconnect();
};
private onclose = () => {
if (!this.forcedClose) {
this.emit("close");
this.reconnect();
}
};
public send: WebSocket["send"] = (
data: string | Blob | ArrayBuffer | ArrayBufferView
) => {
if (this.isopen()) {
this.ws.send(data);
} else if (this.queueMessage) {
this.messageQueue.push(data);
}
};
public close: WebSocket["close"] = (code?: number, reason?: string) => {
this.forcedClose = true;
this.ws.close(code, reason);
};
private connect = () => {
this.ws.onclose = this.onclose;
this.ws.onerror = this.onError;
this.ws.onopen = this.onopen;
this.ws.onmessage = this.onmessage;
if (this.queueMessage) {
this.relayQueuedMessages();
}
};
/** relay messages that were queued while the connection was closed */
private relayQueuedMessages = async () => {
const messageQueue = [...this.messageQueue];
for (const msg of messageQueue) {
await wait(10);
this.ws.send(msg);
}
this.messageQueue.splice(0, messageQueue.length);
};
private reconnect = () => {
if (this.forcedClose || this.disableReconnect) return;
console.log("ws: reconnecting...");
if (this.intervalRef) {
window.clearInterval(this.intervalRef);
}
const TIMEOUT = Math.pow(2, this.retryAttempts + 1) * 1000;
const reconnectHandler = () => {
if (this.reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
this.reconnectAttempts++;
console.log("ws: reconnecting - attempt: ", this.reconnectAttempts);
this.ws = new window.WebSocket(this.ws.url, this.protocols);
this.ws.onopen = this.onrestore;
} else if (this.retryAttempts < this.maxRetryAttempts) {
this.retryAttempts++;
console.log("ws: retrying - attempt: ", this.retryAttempts);
this.reconnectAttempts = 0;
this.reconnect();
} else {
this.emit("reconnection_timeout");
window.clearInterval(this.intervalRef);
}
};
this.intervalRef = window.setInterval(reconnectHandler, TIMEOUT);
};
private onrestore = () => {
console.log("ws: connection restored!");
this.reconnectAttempts = 0;
window.clearInterval(this.intervalRef);
this.emit("open");
this.emit("reconnected");
this.connect();
};
public isopen = () => this.ws.readyState === this.ws.OPEN;
}