1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | import { Observable } from './observable.js'
|
12 | import * as time from './time.js'
|
13 | import * as math from './math.js'
|
14 |
|
15 | const reconnectTimeoutBase = 1200
|
16 | const maxReconnectTimeout = 2500
|
17 |
|
18 | const messageReconnectTimeout = 30000
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | const setupWS = (wsclient) => {
|
24 | if (wsclient.shouldConnect && wsclient.ws === null) {
|
25 | const websocket = new WebSocket(wsclient.url)
|
26 | const binaryType = wsclient.binaryType
|
27 | |
28 |
|
29 |
|
30 | let pingTimeout = null
|
31 | if (binaryType) {
|
32 | websocket.binaryType = binaryType
|
33 | }
|
34 | wsclient.ws = websocket
|
35 | wsclient.connecting = true
|
36 | wsclient.connected = false
|
37 | websocket.onmessage = event => {
|
38 | wsclient.lastMessageReceived = time.getUnixTime()
|
39 | const data = event.data
|
40 | const message = typeof data === 'string' ? JSON.parse(data) : data
|
41 | if (message && message.type === 'pong') {
|
42 | clearTimeout(pingTimeout)
|
43 | pingTimeout = setTimeout(sendPing, messageReconnectTimeout / 2)
|
44 | }
|
45 | wsclient.emit('message', [message, wsclient])
|
46 | }
|
47 | |
48 |
|
49 |
|
50 | const onclose = error => {
|
51 | if (wsclient.ws !== null) {
|
52 | wsclient.ws = null
|
53 | wsclient.connecting = false
|
54 | if (wsclient.connected) {
|
55 | wsclient.connected = false
|
56 | wsclient.emit('disconnect', [{ type: 'disconnect', error }, wsclient])
|
57 | } else {
|
58 | wsclient.unsuccessfulReconnects++
|
59 | }
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | setTimeout(setupWS, math.min(math.log10(wsclient.unsuccessfulReconnects + 1) * reconnectTimeoutBase, maxReconnectTimeout), wsclient)
|
65 | }
|
66 | clearTimeout(pingTimeout)
|
67 | }
|
68 | const sendPing = () => {
|
69 | if (wsclient.ws === websocket) {
|
70 | wsclient.send({
|
71 | type: 'ping'
|
72 | })
|
73 | }
|
74 | }
|
75 | websocket.onclose = () => onclose(null)
|
76 | websocket.onerror = error => onclose(error)
|
77 | websocket.onopen = () => {
|
78 | wsclient.lastMessageReceived = time.getUnixTime()
|
79 | wsclient.connecting = false
|
80 | wsclient.connected = true
|
81 | wsclient.unsuccessfulReconnects = 0
|
82 | wsclient.emit('connect', [{ type: 'connect' }, wsclient])
|
83 |
|
84 | pingTimeout = setTimeout(sendPing, messageReconnectTimeout / 2)
|
85 | }
|
86 | }
|
87 | }
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | export class WebsocketClient extends Observable {
|
93 | |
94 |
|
95 |
|
96 |
|
97 |
|
98 | constructor (url, { binaryType } = {}) {
|
99 | super()
|
100 | this.url = url
|
101 | |
102 |
|
103 |
|
104 | this.ws = null
|
105 | this.binaryType = binaryType || null
|
106 | this.connected = false
|
107 | this.connecting = false
|
108 | this.unsuccessfulReconnects = 0
|
109 | this.lastMessageReceived = 0
|
110 | |
111 |
|
112 |
|
113 |
|
114 | this.shouldConnect = true
|
115 | this._checkInterval = setInterval(() => {
|
116 | if (this.connected && messageReconnectTimeout < time.getUnixTime() - this.lastMessageReceived) {
|
117 |
|
118 |
|
119 | (this.ws).close()
|
120 | }
|
121 | }, messageReconnectTimeout / 2)
|
122 | setupWS(this)
|
123 | }
|
124 |
|
125 | |
126 |
|
127 |
|
128 | send (message) {
|
129 | if (this.ws) {
|
130 | this.ws.send(JSON.stringify(message))
|
131 | }
|
132 | }
|
133 |
|
134 | destroy () {
|
135 | clearInterval(this._checkInterval)
|
136 | this.disconnect()
|
137 | super.destroy()
|
138 | }
|
139 |
|
140 | disconnect () {
|
141 | this.shouldConnect = false
|
142 | if (this.ws !== null) {
|
143 | this.ws.close()
|
144 | }
|
145 | }
|
146 |
|
147 | connect () {
|
148 | this.shouldConnect = true
|
149 | if (!this.connected && this.ws === null) {
|
150 | setupWS(this)
|
151 | }
|
152 | }
|
153 | }
|