reconnecting-websocket-cjs.js 21 KB

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