1
+ // MIT License:
2
+ //
3
+ // Copyright (c) 2010-2012, Joe Walnes
4
+ //
5
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ // of this software and associated documentation files (the "Software"), to deal
7
+ // in the Software without restriction, including without limitation the rights
8
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ // copies of the Software, and to permit persons to whom the Software is
10
+ // furnished to do so, subject to the following conditions:
11
+ //
12
+ // The above copyright notice and this permission notice shall be included in
13
+ // all copies or substantial portions of the Software.
14
+ //
15
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ // THE SOFTWARE.
22
+
23
+ /**
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 successfully connects
26
+ * again.
27
+ *
28
+ * It is API compatible, so when you have:
29
+ * ws = new WebSocket('ws://....');
30
+ * you can replace with:
31
+ * ws = new ReconnectingWebSocket('ws://....');
32
+ *
33
+ * The event stream will typically look like:
34
+ * onconnecting
35
+ * onopen
36
+ * onmessage
37
+ * onmessage
38
+ * onclose // lost connection
39
+ * onconnecting
40
+ * onopen // sometime later...
41
+ * onmessage
42
+ * onmessage
43
+ * etc...
44
+ *
45
+ * It is API compatible with the standard WebSocket API, apart from the following members:
46
+ *
47
+ * - `bufferedAmount`
48
+ * - `extensions`
49
+ * - `binaryType`
50
+ *
51
+ * Latest version: https://github.com/joewalnes/reconnecting-websocket/
52
+ * - Joe Walnes
53
+ *
54
+ * Syntax
55
+ * ======
56
+ * var socket = new ReconnectingWebSocket(url, protocols, options);
57
+ *
58
+ * Parameters
59
+ * ==========
60
+ * url - The url you are connecting to.
61
+ * protocols - Optional string or array of protocols.
62
+ * options - See below
63
+ *
64
+ * Options
65
+ * =======
66
+ * Options can either be passed upon instantiation or set after instantiation:
67
+ *
68
+ * var socket = new ReconnectingWebSocket(url, null, { debug: true, reconnectInterval: 4000 });
69
+ *
70
+ * or
71
+ *
72
+ * var socket = new ReconnectingWebSocket(url);
73
+ * socket.debug = true;
74
+ * socket.reconnectInterval = 4000;
75
+ *
76
+ * debug
77
+ * - Whether this instance should log debug messages. Accepts true or false. Default: false.
78
+ *
79
+ * automaticOpen
80
+ * - Whether or not the websocket should attempt to connect immediately upon instantiation. The socket can be manually opened or closed at any time using ws.open() and ws.close().
81
+ *
82
+ * reconnectInterval
83
+ * - The number of milliseconds to delay before attempting to reconnect. Accepts integer. Default: 1000.
84
+ *
85
+ * maxReconnectInterval
86
+ * - The maximum number of milliseconds to delay a reconnection attempt. Accepts integer. Default: 30000.
87
+ *
88
+ * reconnectDecay
89
+ * - The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. Accepts integer or float. Default: 1.5.
90
+ *
91
+ * timeoutInterval
92
+ * - The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. Accepts integer. Default: 2000.
93
+ *
94
+ */
95
+ ( function ( global , factory ) {
96
+ if ( typeof define === 'function' && define . amd ) {
97
+ define ( [ ] , factory ) ;
98
+ } else if ( typeof module !== 'undefined' && module . exports ) {
99
+ module . exports = factory ( ) ;
100
+ } else {
101
+ global . ReconnectingWebSocket = factory ( ) ;
102
+ }
103
+ } ) ( this , function ( ) {
104
+
105
+ if ( ! ( 'WebSocket' in window ) ) {
106
+ return ;
107
+ }
108
+
109
+ function ReconnectingWebSocket ( url , protocols , options ) {
110
+
111
+ // Default settings
112
+ var settings = {
113
+
114
+ /** Whether this instance should log debug messages. */
115
+ debug : false ,
116
+
117
+ /** Whether or not the websocket should attempt to connect immediately upon instantiation. */
118
+ automaticOpen : true ,
119
+
120
+ /** The number of milliseconds to delay before attempting to reconnect. */
121
+ reconnectInterval : 1000 ,
122
+ /** The maximum number of milliseconds to delay a reconnection attempt. */
123
+ maxReconnectInterval : 30000 ,
124
+ /** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */
125
+ reconnectDecay : 1.5 ,
126
+
127
+ /** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */
128
+ timeoutInterval : 2000 ,
129
+
130
+ /** The maximum number of reconnection attempts to make. Unlimited if null. */
131
+ maxReconnectAttempts : null ,
132
+
133
+ /** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */
134
+ binaryType : 'blob'
135
+ }
136
+ if ( ! options ) { options = { } ; }
137
+
138
+ // Overwrite and define settings with options if they exist.
139
+ for ( var key in settings ) {
140
+ if ( typeof options [ key ] !== 'undefined' ) {
141
+ this [ key ] = options [ key ] ;
142
+ } else {
143
+ this [ key ] = settings [ key ] ;
144
+ }
145
+ }
146
+
147
+ // These should be treated as read-only properties
148
+
149
+ /** The URL as resolved by the constructor. This is always an absolute URL. Read only. */
150
+ this . url = url ;
151
+
152
+ /** The number of attempted reconnects since starting, or the last successful connection. Read only. */
153
+ this . reconnectAttempts = 0 ;
154
+
155
+ /**
156
+ * The current state of the connection.
157
+ * Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
158
+ * Read only.
159
+ */
160
+ this . readyState = WebSocket . CONNECTING ;
161
+
162
+ /**
163
+ * A string indicating the name of the sub-protocol the server selected; this will be one of
164
+ * the strings specified in the protocols parameter when creating the WebSocket object.
165
+ * Read only.
166
+ */
167
+ this . protocol = null ;
168
+
169
+ // Private state variables
170
+
171
+ var self = this ;
172
+ var ws ;
173
+ var forcedClose = false ;
174
+ var timedOut = false ;
175
+ var eventTarget = document . createElement ( 'div' ) ;
176
+
177
+ // Wire up "on*" properties as event handlers
178
+
179
+ eventTarget . addEventListener ( 'open' , function ( event ) { self . onopen ( event ) ; } ) ;
180
+ eventTarget . addEventListener ( 'close' , function ( event ) { self . onclose ( event ) ; } ) ;
181
+ eventTarget . addEventListener ( 'connecting' , function ( event ) { self . onconnecting ( event ) ; } ) ;
182
+ eventTarget . addEventListener ( 'message' , function ( event ) { self . onmessage ( event ) ; } ) ;
183
+ eventTarget . addEventListener ( 'error' , function ( event ) { self . onerror ( event ) ; } ) ;
184
+
185
+ // Expose the API required by EventTarget
186
+
187
+ this . addEventListener = eventTarget . addEventListener . bind ( eventTarget ) ;
188
+ this . removeEventListener = eventTarget . removeEventListener . bind ( eventTarget ) ;
189
+ this . dispatchEvent = eventTarget . dispatchEvent . bind ( eventTarget ) ;
190
+
191
+ /**
192
+ * This function generates an event that is compatible with standard
193
+ * compliant browsers and IE9 - IE11
194
+ *
195
+ * This will prevent the error:
196
+ * Object doesn't support this action
197
+ *
198
+ * http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563
199
+ * @param s String The name that the event should use
200
+ * @param args Object an optional object that the event will use
201
+ */
202
+ function generateEvent ( s , args ) {
203
+ var evt = document . createEvent ( "CustomEvent" ) ;
204
+ evt . initCustomEvent ( s , false , false , args ) ;
205
+ return evt ;
206
+ } ;
207
+
208
+ this . open = function ( reconnectAttempt ) {
209
+ ws = new WebSocket ( self . url , protocols || [ ] ) ;
210
+ ws . binaryType = this . binaryType ;
211
+
212
+ if ( reconnectAttempt ) {
213
+ if ( this . maxReconnectAttempts && this . reconnectAttempts > this . maxReconnectAttempts ) {
214
+ return ;
215
+ }
216
+ } else {
217
+ eventTarget . dispatchEvent ( generateEvent ( 'connecting' ) ) ;
218
+ this . reconnectAttempts = 0 ;
219
+ }
220
+
221
+ if ( self . debug || ReconnectingWebSocket . debugAll ) {
222
+ console . debug ( 'ReconnectingWebSocket' , 'attempt-connect' , self . url ) ;
223
+ }
224
+
225
+ var localWs = ws ;
226
+ var timeout = setTimeout ( function ( ) {
227
+ if ( self . debug || ReconnectingWebSocket . debugAll ) {
228
+ console . debug ( 'ReconnectingWebSocket' , 'connection-timeout' , self . url ) ;
229
+ }
230
+ timedOut = true ;
231
+ localWs . close ( ) ;
232
+ timedOut = false ;
233
+ } , self . timeoutInterval ) ;
234
+
235
+ ws . onopen = function ( event ) {
236
+ clearTimeout ( timeout ) ;
237
+ if ( self . debug || ReconnectingWebSocket . debugAll ) {
238
+ console . debug ( 'ReconnectingWebSocket' , 'onopen' , self . url ) ;
239
+ }
240
+ self . protocol = ws . protocol ;
241
+ self . readyState = WebSocket . OPEN ;
242
+ self . reconnectAttempts = 0 ;
243
+ var e = generateEvent ( 'open' ) ;
244
+ e . isReconnect = reconnectAttempt ;
245
+ reconnectAttempt = false ;
246
+ eventTarget . dispatchEvent ( e ) ;
247
+ } ;
248
+
249
+ ws . onclose = function ( event ) {
250
+ clearTimeout ( timeout ) ;
251
+ ws = null ;
252
+ if ( forcedClose ) {
253
+ self . readyState = WebSocket . CLOSED ;
254
+ eventTarget . dispatchEvent ( generateEvent ( 'close' ) ) ;
255
+ } else {
256
+ self . readyState = WebSocket . CONNECTING ;
257
+ var e = generateEvent ( 'connecting' ) ;
258
+ e . code = event . code ;
259
+ e . reason = event . reason ;
260
+ e . wasClean = event . wasClean ;
261
+ eventTarget . dispatchEvent ( e ) ;
262
+ if ( ! reconnectAttempt && ! timedOut ) {
263
+ if ( self . debug || ReconnectingWebSocket . debugAll ) {
264
+ console . debug ( 'ReconnectingWebSocket' , 'onclose' , self . url ) ;
265
+ }
266
+ eventTarget . dispatchEvent ( generateEvent ( 'close' ) ) ;
267
+ }
268
+
269
+ var timeout = self . reconnectInterval * Math . pow ( self . reconnectDecay , self . reconnectAttempts ) ;
270
+ setTimeout ( function ( ) {
271
+ self . reconnectAttempts ++ ;
272
+ self . open ( true ) ;
273
+ } , timeout > self . maxReconnectInterval ? self . maxReconnectInterval : timeout ) ;
274
+ }
275
+ } ;
276
+ ws . onmessage = function ( event ) {
277
+ if ( self . debug || ReconnectingWebSocket . debugAll ) {
278
+ console . debug ( 'ReconnectingWebSocket' , 'onmessage' , self . url , event . data ) ;
279
+ }
280
+ var e = generateEvent ( 'message' ) ;
281
+ e . data = event . data ;
282
+ eventTarget . dispatchEvent ( e ) ;
283
+ } ;
284
+ ws . onerror = function ( event ) {
285
+ if ( self . debug || ReconnectingWebSocket . debugAll ) {
286
+ console . debug ( 'ReconnectingWebSocket' , 'onerror' , self . url , event ) ;
287
+ }
288
+ eventTarget . dispatchEvent ( generateEvent ( 'error' ) ) ;
289
+ } ;
290
+ }
291
+
292
+ // Whether or not to create a websocket upon instantiation
293
+ if ( this . automaticOpen == true ) {
294
+ this . open ( false ) ;
295
+ }
296
+
297
+ /**
298
+ * Transmits data to the server over the WebSocket connection.
299
+ *
300
+ * @param data a text string, ArrayBuffer or Blob to send to the server.
301
+ */
302
+ this . send = function ( data ) {
303
+ if ( ws ) {
304
+ if ( self . debug || ReconnectingWebSocket . debugAll ) {
305
+ console . debug ( 'ReconnectingWebSocket' , 'send' , self . url , data ) ;
306
+ }
307
+ return ws . send ( data ) ;
308
+ } else {
309
+ throw 'INVALID_STATE_ERR : Pausing to reconnect websocket' ;
310
+ }
311
+ } ;
312
+
313
+ /**
314
+ * Closes the WebSocket connection or connection attempt, if any.
315
+ * If the connection is already CLOSED, this method does nothing.
316
+ */
317
+ this . close = function ( code , reason ) {
318
+ // Default CLOSE_NORMAL code
319
+ if ( typeof code == 'undefined' ) {
320
+ code = 1000 ;
321
+ }
322
+ forcedClose = true ;
323
+ if ( ws ) {
324
+ ws . close ( code , reason ) ;
325
+ }
326
+ } ;
327
+
328
+ /**
329
+ * Additional public API method to refresh the connection if still open (close, re-open).
330
+ * For example, if the app suspects bad data / missed heart beats, it can try to refresh.
331
+ */
332
+ this . refresh = function ( ) {
333
+ if ( ws ) {
334
+ ws . close ( ) ;
335
+ }
336
+ } ;
337
+ }
338
+
339
+ /**
340
+ * An event listener to be called when the WebSocket connection's readyState changes to OPEN;
341
+ * this indicates that the connection is ready to send and receive data.
342
+ */
343
+ ReconnectingWebSocket . prototype . onopen = function ( event ) { } ;
344
+ /** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */
345
+ ReconnectingWebSocket . prototype . onclose = function ( event ) { } ;
346
+ /** An event listener to be called when a connection begins being attempted. */
347
+ ReconnectingWebSocket . prototype . onconnecting = function ( event ) { } ;
348
+ /** An event listener to be called when a message is received from the server. */
349
+ ReconnectingWebSocket . prototype . onmessage = function ( event ) { } ;
350
+ /** An event listener to be called when an error occurs. */
351
+ ReconnectingWebSocket . prototype . onerror = function ( event ) { } ;
352
+
353
+ /**
354
+ * Whether all instances of ReconnectingWebSocket should log debug messages.
355
+ * Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true.
356
+ */
357
+ ReconnectingWebSocket . debugAll = false ;
358
+
359
+ ReconnectingWebSocket . CONNECTING = WebSocket . CONNECTING ;
360
+ ReconnectingWebSocket . OPEN = WebSocket . OPEN ;
361
+ ReconnectingWebSocket . CLOSING = WebSocket . CLOSING ;
362
+ ReconnectingWebSocket . CLOSED = WebSocket . CLOSED ;
363
+
364
+ return ReconnectingWebSocket ;
365
+ } ) ;
0 commit comments