UNPKG

24.3 kBJavaScriptView Raw
1var ReconnectingWebSocket = (function () {
2 'use strict';
3
4 /*! *****************************************************************************
5 Copyright (c) Microsoft Corporation. All rights reserved.
6 Licensed under the Apache License, Version 2.0 (the "License"); you may not use
7 this file except in compliance with the License. You may obtain a copy of the
8 License at http://www.apache.org/licenses/LICENSE-2.0
9
10 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
12 WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
13 MERCHANTABLITY OR NON-INFRINGEMENT.
14
15 See the Apache Version 2.0 License for specific language governing permissions
16 and limitations under the License.
17 ***************************************************************************** */
18 /* global Reflect, Promise */
19
20 var extendStatics = function(d, b) {
21 extendStatics = Object.setPrototypeOf ||
22 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
23 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
24 return extendStatics(d, b);
25 };
26
27 function __extends(d, b) {
28 extendStatics(d, b);
29 function __() { this.constructor = d; }
30 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
31 }
32
33 function __values(o) {
34 var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
35 if (m) return m.call(o);
36 return {
37 next: function () {
38 if (o && i >= o.length) o = void 0;
39 return { value: o && o[i++], done: !o };
40 }
41 };
42 }
43
44 function __read(o, n) {
45 var m = typeof Symbol === "function" && o[Symbol.iterator];
46 if (!m) return o;
47 var i = m.call(o), r, ar = [], e;
48 try {
49 while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
50 }
51 catch (error) { e = { error: error }; }
52 finally {
53 try {
54 if (r && !r.done && (m = i["return"])) m.call(i);
55 }
56 finally { if (e) throw e.error; }
57 }
58 return ar;
59 }
60
61 function __spread() {
62 for (var ar = [], i = 0; i < arguments.length; i++)
63 ar = ar.concat(__read(arguments[i]));
64 return ar;
65 }
66
67 var Event = /** @class */ (function () {
68 function Event(type, target) {
69 this.target = target;
70 this.type = type;
71 }
72 return Event;
73 }());
74 var ErrorEvent = /** @class */ (function (_super) {
75 __extends(ErrorEvent, _super);
76 function ErrorEvent(error, target) {
77 var _this = _super.call(this, 'error', target) || this;
78 _this.message = error.message;
79 _this.error = error;
80 return _this;
81 }
82 return ErrorEvent;
83 }(Event));
84 var CloseEvent = /** @class */ (function (_super) {
85 __extends(CloseEvent, _super);
86 function CloseEvent(code, reason, target) {
87 if (code === void 0) { code = 1000; }
88 if (reason === void 0) { reason = ''; }
89 var _this = _super.call(this, 'close', target) || this;
90 _this.wasClean = true;
91 _this.code = code;
92 _this.reason = reason;
93 return _this;
94 }
95 return CloseEvent;
96 }(Event));
97
98 /*!
99 * Reconnecting WebSocket
100 * by Pedro Ladaria <pedro.ladaria@gmail.com>
101 * https://github.com/pladaria/reconnecting-websocket
102 * License MIT
103 */
104 var getGlobalWebSocket = function () {
105 if (typeof WebSocket !== 'undefined') {
106 // @ts-ignore
107 return WebSocket;
108 }
109 };
110 /**
111 * Returns true if given argument looks like a WebSocket class
112 */
113 var isWebSocket = function (w) { return typeof w !== 'undefined' && !!w && w.CLOSING === 2; };
114 var DEFAULT = {
115 maxReconnectionDelay: 10000,
116 minReconnectionDelay: 1000 + Math.random() * 4000,
117 minUptime: 5000,
118 reconnectionDelayGrowFactor: 1.3,
119 connectionTimeout: 4000,
120 maxRetries: Infinity,
121 maxEnqueuedMessages: Infinity,
122 startClosed: false,
123 debug: false,
124 };
125 var ReconnectingWebSocket = /** @class */ (function () {
126 function ReconnectingWebSocket(url, protocols, options) {
127 var _this = this;
128 if (options === void 0) { options = {}; }
129 this._listeners = {
130 error: [],
131 message: [],
132 open: [],
133 close: [],
134 };
135 this._retryCount = -1;
136 this._shouldReconnect = true;
137 this._connectLock = false;
138 this._binaryType = 'blob';
139 this._closeCalled = false;
140 this._messageQueue = [];
141 /**
142 * An event listener to be called when the WebSocket connection's readyState changes to CLOSED
143 */
144 this.onclose = null;
145 /**
146 * An event listener to be called when an error occurs
147 */
148 this.onerror = null;
149 /**
150 * An event listener to be called when a message is received from the server
151 */
152 this.onmessage = null;
153 /**
154 * An event listener to be called when the WebSocket connection's readyState changes to OPEN;
155 * this indicates that the connection is ready to send and receive data
156 */
157 this.onopen = null;
158 this._handleOpen = function (event) {
159 _this._debug('open event');
160 var _a = _this._options.minUptime, minUptime = _a === void 0 ? DEFAULT.minUptime : _a;
161 clearTimeout(_this._connectTimeout);
162 _this._uptimeTimeout = setTimeout(function () { return _this._acceptOpen(); }, minUptime);
163 _this._ws.binaryType = _this._binaryType;
164 // send enqueued messages (messages sent before websocket open event)
165 _this._messageQueue.forEach(function (message) { return _this._ws.send(message); });
166 _this._messageQueue = [];
167 if (_this.onopen) {
168 _this.onopen(event);
169 }
170 _this._listeners.open.forEach(function (listener) { return _this._callEventListener(event, listener); });
171 };
172 this._handleMessage = function (event) {
173 _this._debug('message event');
174 if (_this.onmessage) {
175 _this.onmessage(event);
176 }
177 _this._listeners.message.forEach(function (listener) { return _this._callEventListener(event, listener); });
178 };
179 this._handleError = function (event) {
180 _this._debug('error event', event.message);
181 _this._disconnect(undefined, event.message === 'TIMEOUT' ? 'timeout' : undefined);
182 if (_this.onerror) {
183 _this.onerror(event);
184 }
185 _this._debug('exec error listeners');
186 _this._listeners.error.forEach(function (listener) { return _this._callEventListener(event, listener); });
187 _this._connect();
188 };
189 this._handleClose = function (event) {
190 _this._debug('close event');
191 _this._clearTimeouts();
192 if (_this._shouldReconnect) {
193 _this._connect();
194 }
195 if (_this.onclose) {
196 _this.onclose(event);
197 }
198 _this._listeners.close.forEach(function (listener) { return _this._callEventListener(event, listener); });
199 };
200 this._url = url;
201 this._protocols = protocols;
202 this._options = options;
203 if (this._options.startClosed) {
204 this._shouldReconnect = false;
205 }
206 this._connect();
207 }
208 Object.defineProperty(ReconnectingWebSocket, "CONNECTING", {
209 get: function () {
210 return 0;
211 },
212 enumerable: true,
213 configurable: true
214 });
215 Object.defineProperty(ReconnectingWebSocket, "OPEN", {
216 get: function () {
217 return 1;
218 },
219 enumerable: true,
220 configurable: true
221 });
222 Object.defineProperty(ReconnectingWebSocket, "CLOSING", {
223 get: function () {
224 return 2;
225 },
226 enumerable: true,
227 configurable: true
228 });
229 Object.defineProperty(ReconnectingWebSocket, "CLOSED", {
230 get: function () {
231 return 3;
232 },
233 enumerable: true,
234 configurable: true
235 });
236 Object.defineProperty(ReconnectingWebSocket.prototype, "CONNECTING", {
237 get: function () {
238 return ReconnectingWebSocket.CONNECTING;
239 },
240 enumerable: true,
241 configurable: true
242 });
243 Object.defineProperty(ReconnectingWebSocket.prototype, "OPEN", {
244 get: function () {
245 return ReconnectingWebSocket.OPEN;
246 },
247 enumerable: true,
248 configurable: true
249 });
250 Object.defineProperty(ReconnectingWebSocket.prototype, "CLOSING", {
251 get: function () {
252 return ReconnectingWebSocket.CLOSING;
253 },
254 enumerable: true,
255 configurable: true
256 });
257 Object.defineProperty(ReconnectingWebSocket.prototype, "CLOSED", {
258 get: function () {
259 return ReconnectingWebSocket.CLOSED;
260 },
261 enumerable: true,
262 configurable: true
263 });
264 Object.defineProperty(ReconnectingWebSocket.prototype, "binaryType", {
265 get: function () {
266 return this._ws ? this._ws.binaryType : this._binaryType;
267 },
268 set: function (value) {
269 this._binaryType = value;
270 if (this._ws) {
271 this._ws.binaryType = value;
272 }
273 },
274 enumerable: true,
275 configurable: true
276 });
277 Object.defineProperty(ReconnectingWebSocket.prototype, "retryCount", {
278 /**
279 * Returns the number or connection retries
280 */
281 get: function () {
282 return Math.max(this._retryCount, 0);
283 },
284 enumerable: true,
285 configurable: true
286 });
287 Object.defineProperty(ReconnectingWebSocket.prototype, "bufferedAmount", {
288 /**
289 * The number of bytes of data that have been queued using calls to send() but not yet
290 * transmitted to the network. This value resets to zero once all queued data has been sent.
291 * This value does not reset to zero when the connection is closed; if you keep calling send(),
292 * this will continue to climb. Read only
293 */
294 get: function () {
295 var bytes = this._messageQueue.reduce(function (acc, message) {
296 if (typeof message === 'string') {
297 acc += message.length; // not byte size
298 }
299 else if (message instanceof Blob) {
300 acc += message.size;
301 }
302 else {
303 acc += message.byteLength;
304 }
305 return acc;
306 }, 0);
307 return bytes + (this._ws ? this._ws.bufferedAmount : 0);
308 },
309 enumerable: true,
310 configurable: true
311 });
312 Object.defineProperty(ReconnectingWebSocket.prototype, "extensions", {
313 /**
314 * The extensions selected by the server. This is currently only the empty string or a list of
315 * extensions as negotiated by the connection
316 */
317 get: function () {
318 return this._ws ? this._ws.extensions : '';
319 },
320 enumerable: true,
321 configurable: true
322 });
323 Object.defineProperty(ReconnectingWebSocket.prototype, "protocol", {
324 /**
325 * A string indicating the name of the sub-protocol the server selected;
326 * this will be one of the strings specified in the protocols parameter when creating the
327 * WebSocket object
328 */
329 get: function () {
330 return this._ws ? this._ws.protocol : '';
331 },
332 enumerable: true,
333 configurable: true
334 });
335 Object.defineProperty(ReconnectingWebSocket.prototype, "readyState", {
336 /**
337 * The current state of the connection; this is one of the Ready state constants
338 */
339 get: function () {
340 if (this._ws) {
341 return this._ws.readyState;
342 }
343 return this._options.startClosed
344 ? ReconnectingWebSocket.CLOSED
345 : ReconnectingWebSocket.CONNECTING;
346 },
347 enumerable: true,
348 configurable: true
349 });
350 Object.defineProperty(ReconnectingWebSocket.prototype, "url", {
351 /**
352 * The URL as resolved by the constructor
353 */
354 get: function () {
355 return this._ws ? this._ws.url : '';
356 },
357 enumerable: true,
358 configurable: true
359 });
360 /**
361 * Closes the WebSocket connection or connection attempt, if any. If the connection is already
362 * CLOSED, this method does nothing
363 */
364 ReconnectingWebSocket.prototype.close = function (code, reason) {
365 if (code === void 0) { code = 1000; }
366 this._closeCalled = true;
367 this._shouldReconnect = false;
368 this._clearTimeouts();
369 if (!this._ws) {
370 this._debug('close enqueued: no ws instance');
371 return;
372 }
373 if (this._ws.readyState === this.CLOSED) {
374 this._debug('close: already closed');
375 return;
376 }
377 this._ws.close(code, reason);
378 };
379 /**
380 * Closes the WebSocket connection or connection attempt and connects again.
381 * Resets retry counter;
382 */
383 ReconnectingWebSocket.prototype.reconnect = function (code, reason) {
384 this._shouldReconnect = true;
385 this._closeCalled = false;
386 this._retryCount = -1;
387 if (!this._ws || this._ws.readyState === this.CLOSED) {
388 this._connect();
389 }
390 else {
391 this._disconnect(code, reason);
392 this._connect();
393 }
394 };
395 /**
396 * Enqueue specified data to be transmitted to the server over the WebSocket connection
397 */
398 ReconnectingWebSocket.prototype.send = function (data) {
399 if (this._ws && this._ws.readyState === this.OPEN) {
400 this._debug('send', data);
401 this._ws.send(data);
402 }
403 else {
404 var _a = this._options.maxEnqueuedMessages, maxEnqueuedMessages = _a === void 0 ? DEFAULT.maxEnqueuedMessages : _a;
405 if (this._messageQueue.length < maxEnqueuedMessages) {
406 this._debug('enqueue', data);
407 this._messageQueue.push(data);
408 }
409 }
410 };
411 /**
412 * Register an event handler of a specific event type
413 */
414 ReconnectingWebSocket.prototype.addEventListener = function (type, listener) {
415 if (this._listeners[type]) {
416 // @ts-ignore
417 this._listeners[type].push(listener);
418 }
419 };
420 ReconnectingWebSocket.prototype.dispatchEvent = function (event) {
421 var e_1, _a;
422 var listeners = this._listeners[event.type];
423 if (listeners) {
424 try {
425 for (var listeners_1 = __values(listeners), listeners_1_1 = listeners_1.next(); !listeners_1_1.done; listeners_1_1 = listeners_1.next()) {
426 var listener = listeners_1_1.value;
427 this._callEventListener(event, listener);
428 }
429 }
430 catch (e_1_1) { e_1 = { error: e_1_1 }; }
431 finally {
432 try {
433 if (listeners_1_1 && !listeners_1_1.done && (_a = listeners_1.return)) _a.call(listeners_1);
434 }
435 finally { if (e_1) throw e_1.error; }
436 }
437 }
438 return true;
439 };
440 /**
441 * Removes an event listener
442 */
443 ReconnectingWebSocket.prototype.removeEventListener = function (type, listener) {
444 if (this._listeners[type]) {
445 // @ts-ignore
446 this._listeners[type] = this._listeners[type].filter(function (l) { return l !== listener; });
447 }
448 };
449 ReconnectingWebSocket.prototype._debug = function () {
450 var args = [];
451 for (var _i = 0; _i < arguments.length; _i++) {
452 args[_i] = arguments[_i];
453 }
454 if (this._options.debug) {
455 // not using spread because compiled version uses Symbols
456 // tslint:disable-next-line
457 console.log.apply(console, __spread(['RWS>'], args));
458 }
459 };
460 ReconnectingWebSocket.prototype._getNextDelay = function () {
461 var _a = this._options, _b = _a.reconnectionDelayGrowFactor, reconnectionDelayGrowFactor = _b === void 0 ? DEFAULT.reconnectionDelayGrowFactor : _b, _c = _a.minReconnectionDelay, minReconnectionDelay = _c === void 0 ? DEFAULT.minReconnectionDelay : _c, _d = _a.maxReconnectionDelay, maxReconnectionDelay = _d === void 0 ? DEFAULT.maxReconnectionDelay : _d;
462 var delay = 0;
463 if (this._retryCount > 0) {
464 delay =
465 minReconnectionDelay * Math.pow(reconnectionDelayGrowFactor, this._retryCount - 1);
466 if (delay > maxReconnectionDelay) {
467 delay = maxReconnectionDelay;
468 }
469 }
470 this._debug('next delay', delay);
471 return delay;
472 };
473 ReconnectingWebSocket.prototype._wait = function () {
474 var _this = this;
475 return new Promise(function (resolve) {
476 setTimeout(resolve, _this._getNextDelay());
477 });
478 };
479 ReconnectingWebSocket.prototype._getNextUrl = function (urlProvider) {
480 if (typeof urlProvider === 'string') {
481 return Promise.resolve(urlProvider);
482 }
483 if (typeof urlProvider === 'function') {
484 var url = urlProvider();
485 if (typeof url === 'string') {
486 return Promise.resolve(url);
487 }
488 if (!!url.then) {
489 return url;
490 }
491 }
492 throw Error('Invalid URL');
493 };
494 ReconnectingWebSocket.prototype._connect = function () {
495 var _this = this;
496 if (this._connectLock || !this._shouldReconnect) {
497 return;
498 }
499 this._connectLock = true;
500 var _a = this._options, _b = _a.maxRetries, maxRetries = _b === void 0 ? DEFAULT.maxRetries : _b, _c = _a.connectionTimeout, connectionTimeout = _c === void 0 ? DEFAULT.connectionTimeout : _c, _d = _a.WebSocket, WebSocket = _d === void 0 ? getGlobalWebSocket() : _d;
501 if (this._retryCount >= maxRetries) {
502 this._debug('max retries reached', this._retryCount, '>=', maxRetries);
503 return;
504 }
505 this._retryCount++;
506 this._debug('connect', this._retryCount);
507 this._removeListeners();
508 if (!isWebSocket(WebSocket)) {
509 throw Error('No valid WebSocket class provided');
510 }
511 this._wait()
512 .then(function () { return _this._getNextUrl(_this._url); })
513 .then(function (url) {
514 // close could be called before creating the ws
515 if (_this._closeCalled) {
516 return;
517 }
518 _this._debug('connect', { url: url, protocols: _this._protocols });
519 _this._ws = _this._protocols
520 ? new WebSocket(url, _this._protocols)
521 : new WebSocket(url);
522 _this._ws.binaryType = _this._binaryType;
523 _this._connectLock = false;
524 _this._addListeners();
525 _this._connectTimeout = setTimeout(function () { return _this._handleTimeout(); }, connectionTimeout);
526 });
527 };
528 ReconnectingWebSocket.prototype._handleTimeout = function () {
529 this._debug('timeout event');
530 this._handleError(new ErrorEvent(Error('TIMEOUT'), this));
531 };
532 ReconnectingWebSocket.prototype._disconnect = function (code, reason) {
533 if (code === void 0) { code = 1000; }
534 this._clearTimeouts();
535 if (!this._ws) {
536 return;
537 }
538 this._removeListeners();
539 try {
540 this._ws.close(code, reason);
541 this._handleClose(new CloseEvent(code, reason, this));
542 }
543 catch (error) {
544 // ignore
545 }
546 };
547 ReconnectingWebSocket.prototype._acceptOpen = function () {
548 this._debug('accept open');
549 this._retryCount = 0;
550 };
551 ReconnectingWebSocket.prototype._callEventListener = function (event, listener) {
552 if ('handleEvent' in listener) {
553 // @ts-ignore
554 listener.handleEvent(event);
555 }
556 else {
557 // @ts-ignore
558 listener(event);
559 }
560 };
561 ReconnectingWebSocket.prototype._removeListeners = function () {
562 if (!this._ws) {
563 return;
564 }
565 this._debug('removeListeners');
566 this._ws.removeEventListener('open', this._handleOpen);
567 this._ws.removeEventListener('close', this._handleClose);
568 this._ws.removeEventListener('message', this._handleMessage);
569 // @ts-ignore
570 this._ws.removeEventListener('error', this._handleError);
571 };
572 ReconnectingWebSocket.prototype._addListeners = function () {
573 if (!this._ws) {
574 return;
575 }
576 this._debug('addListeners');
577 this._ws.addEventListener('open', this._handleOpen);
578 this._ws.addEventListener('close', this._handleClose);
579 this._ws.addEventListener('message', this._handleMessage);
580 // @ts-ignore
581 this._ws.addEventListener('error', this._handleError);
582 };
583 ReconnectingWebSocket.prototype._clearTimeouts = function () {
584 clearTimeout(this._connectTimeout);
585 clearTimeout(this._uptimeTimeout);
586 };
587 return ReconnectingWebSocket;
588 }());
589
590 return ReconnectingWebSocket;
591
592}());