UNPKG

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