|
22 | 22 |
|
23 | 23 | /** |
24 | 24 | * This behaves like a WebSocket in every way, except if it fails to connect, |
25 | | - * or it gets disconnected, it will repeatedly poll until it succesfully connects |
| 25 | + * or it gets disconnected, it will repeatedly poll until it successfully connects |
26 | 26 | * again. |
27 | 27 | * |
28 | 28 | * It is API compatible, so when you have: |
|
42 | 42 | * onmessage |
43 | 43 | * etc... |
44 | 44 | * |
45 | | - * It is API compatible with the standard WebSocket API. |
| 45 | + * It is API compatible with the standard WebSocket API, apart from the following members: |
| 46 | + * |
| 47 | + * - `bufferedAmount` |
| 48 | + * - `extensions` |
| 49 | + * - `binaryType` |
46 | 50 | * |
47 | 51 | * Latest version: https://github.com/joewalnes/reconnecting-websocket/ |
48 | 52 | * - Joe Walnes |
|
58 | 62 | })(this, function () { |
59 | 63 |
|
60 | 64 | function ReconnectingWebSocket(url, protocols) { |
61 | | - protocols = protocols || []; |
62 | | - |
63 | 65 | // These can be altered by calling code. |
| 66 | + |
| 67 | + /** Whether this instance should log debug messages. */ |
64 | 68 | this.debug = false; |
| 69 | + /** The number of milliseconds to delay before attempting to reconnect. */ |
65 | 70 | this.reconnectInterval = 1000; |
| 71 | + /** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */ |
66 | 72 | this.reconnectDecay = 1.5; |
| 73 | + /** The number of attempted reconnects since starting, or the last successful connection. */ |
67 | 74 | this.reconnectAttempts = 0; |
| 75 | + /** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */ |
68 | 76 | this.timeoutInterval = 2000; |
69 | 77 |
|
| 78 | + // These should be treated as read-only properties |
| 79 | + |
| 80 | + /** The URL as resolved by the constructor. This is always an absolute URL. Read only. */ |
| 81 | + this.url = url; |
| 82 | + /** |
| 83 | + * The current state of the connection. |
| 84 | + * Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED |
| 85 | + * Read only. |
| 86 | + */ |
| 87 | + this.readyState = WebSocket.CONNECTING; |
| 88 | + /** |
| 89 | + * A string indicating the name of the sub-protocol the server selected; this will be one of |
| 90 | + * the strings specified in the protocols parameter when creating the WebSocket object. |
| 91 | + * Read only. |
| 92 | + */ |
| 93 | + this.protocol = null; |
| 94 | + |
| 95 | + // Private state variables |
| 96 | + |
70 | 97 | var self = this; |
71 | 98 | var ws; |
72 | 99 | var forcedClose = false; |
73 | 100 | var timedOut = false; |
74 | | - |
75 | | - this.url = url; |
76 | | - this.protocols = protocols; |
77 | | - this.readyState = WebSocket.CONNECTING; |
78 | | - this.URL = url; // Public API |
| 101 | + var eventTarget = document.createElement('div'); |
79 | 102 |
|
80 | | - this.onopen = function(event) { |
81 | | - }; |
| 103 | + // Wire up "on*" properties as event handlers |
82 | 104 |
|
83 | | - this.onclose = function(event) { |
84 | | - }; |
| 105 | + eventTarget.addEventListener('open', function(event) { self.onopen(event); }); |
| 106 | + eventTarget.addEventListener('close', function(event) { self.onclose(event); }); |
| 107 | + eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); }); |
| 108 | + eventTarget.addEventListener('message', function(event) { self.onmessage(event); }); |
| 109 | + eventTarget.addEventListener('error', function(event) { self.onerror(event); }); |
85 | 110 |
|
86 | | - this.onconnecting = function(event) { |
87 | | - }; |
| 111 | + // Expose the API required by EventTarget |
88 | 112 |
|
89 | | - this.onmessage = function(event) { |
90 | | - }; |
91 | | - |
92 | | - this.onerror = function(event) { |
93 | | - }; |
| 113 | + this.addEventListener = eventTarget.addEventListener.bind(eventTarget); |
| 114 | + this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget); |
| 115 | + this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget); |
94 | 116 |
|
95 | 117 | function connect(reconnectAttempt) { |
96 | | - ws = new WebSocket(url, protocols); |
97 | | - |
98 | | - if(!reconnectAttempt) |
99 | | - self.onconnecting(); |
100 | | - |
| 118 | + ws = new WebSocket(url, protocols || []); |
| 119 | + |
| 120 | + if (!reconnectAttempt) { |
| 121 | + eventTarget.dispatchEvent(new Event('connecting')); |
| 122 | + } |
| 123 | + |
101 | 124 | if (self.debug || ReconnectingWebSocket.debugAll) { |
102 | 125 | console.debug('ReconnectingWebSocket', 'attempt-connect', url); |
103 | 126 | } |
|
117 | 140 | if (self.debug || ReconnectingWebSocket.debugAll) { |
118 | 141 | console.debug('ReconnectingWebSocket', 'onopen', url); |
119 | 142 | } |
| 143 | + self.protocol = ws.protocol; |
120 | 144 | self.readyState = WebSocket.OPEN; |
121 | | - reconnectAttempt = false; |
122 | 145 | self.reconnectAttempts = 0; |
123 | | - self.onopen(event); |
| 146 | + var e = new Event('open'); |
| 147 | + e.isReconnect = reconnectAttempt; |
| 148 | + reconnectAttempt = false; |
| 149 | + eventTarget.dispatchEvent(e); |
124 | 150 | }; |
125 | 151 |
|
126 | 152 | ws.onclose = function(event) { |
|
129 | 155 | if (forcedClose) { |
130 | 156 | self.readyState = WebSocket.CLOSED; |
131 | 157 | self.onclose(event); |
| 158 | + eventTarget.dispatchEvent(new Event('close')); |
132 | 159 | } else { |
133 | 160 | self.readyState = WebSocket.CONNECTING; |
134 | | - self.onconnecting(); |
| 161 | + eventTarget.dispatchEvent(new Event('connecting')); |
135 | 162 | if (!reconnectAttempt && !timedOut) { |
136 | 163 | if (self.debug || ReconnectingWebSocket.debugAll) { |
137 | 164 | console.debug('ReconnectingWebSocket', 'onclose', url); |
138 | 165 | } |
139 | | - self.onclose(event); |
| 166 | + eventTarget.dispatchEvent(new Event('close')); |
140 | 167 | } |
141 | 168 | setTimeout(function() { |
142 | 169 | self.reconnectAttempts++; |
|
148 | 175 | if (self.debug || ReconnectingWebSocket.debugAll) { |
149 | 176 | console.debug('ReconnectingWebSocket', 'onmessage', url, event.data); |
150 | 177 | } |
151 | | - self.onmessage(event); |
| 178 | + var e = new Event('message'); |
| 179 | + e.data = event.data; |
| 180 | + eventTarget.dispatchEvent(e); |
152 | 181 | }; |
153 | 182 | ws.onerror = function(event) { |
154 | 183 | if (self.debug || ReconnectingWebSocket.debugAll) { |
155 | 184 | console.debug('ReconnectingWebSocket', 'onerror', url, event); |
156 | 185 | } |
157 | | - self.onerror(event); |
| 186 | + eventTarget.dispatchEvent(new Event('event')); |
158 | 187 | }; |
159 | 188 | } |
160 | 189 | connect(false); |
161 | 190 |
|
| 191 | + /** |
| 192 | + * Transmits data to the server over the WebSocket connection. |
| 193 | + * |
| 194 | + * @param data a text string, ArrayBuffer or Blob to send to the server. |
| 195 | + */ |
162 | 196 | this.send = function(data) { |
163 | 197 | if (ws) { |
164 | 198 | if (self.debug || ReconnectingWebSocket.debugAll) { |
|
170 | 204 | } |
171 | 205 | }; |
172 | 206 |
|
173 | | - this.close = function() { |
| 207 | + /** |
| 208 | + * Closes the WebSocket connection or connection attempt, if any. |
| 209 | + * If the connection is already CLOSED, this method does nothing. |
| 210 | + */ |
| 211 | + this.close = function(code, reason) { |
174 | 212 | forcedClose = true; |
175 | 213 | if (ws) { |
176 | | - ws.close(); |
| 214 | + ws.close(code, reason); |
177 | 215 | } |
178 | 216 | }; |
179 | 217 |
|
|
189 | 227 | } |
190 | 228 |
|
191 | 229 | /** |
| 230 | + * An event listener to be called when the WebSocket connection's readyState changes to OPEN; |
| 231 | + * this indicates that the connection is ready to send and receive data. |
| 232 | + */ |
| 233 | + ReconnectingWebSocket.prototype.onopen = function(event) {}; |
| 234 | + /** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */ |
| 235 | + ReconnectingWebSocket.prototype.onclose = function(event) {}; |
| 236 | + /** An event listener to be called when a connection begins being attempted. */ |
| 237 | + ReconnectingWebSocket.prototype.onconnecting = function(event) {}; |
| 238 | + /** An event listener to be called when a message is received from the server. */ |
| 239 | + ReconnectingWebSocket.prototype.onmessage = function(event) {}; |
| 240 | + /** An event listener to be called when an error occurs. */ |
| 241 | + ReconnectingWebSocket.prototype.onerror = function(event) {}; |
| 242 | + |
| 243 | + /** |
| 244 | + * Whether all instances of ReconnectingWebSocket should log debug messages. |
192 | 245 | * Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true. |
193 | 246 | */ |
194 | 247 | ReconnectingWebSocket.debugAll = false; |
195 | 248 |
|
| 249 | + ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING; |
| 250 | + ReconnectingWebSocket.OPEN = WebSocket.OPEN; |
| 251 | + ReconnectingWebSocket.CLOSING = WebSocket.CLOSING; |
| 252 | + ReconnectingWebSocket.CLOSED = WebSocket.CLOSED; |
| 253 | + |
196 | 254 | return ReconnectingWebSocket; |
197 | 255 | }); |
0 commit comments