reconnecting-websocket-mjs.js 21 KB

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