reconnecting-websocket-cjs.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. 'use strict';
  2. function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
  3. var RNTimer = _interopDefault(require('react-native-background-timer'));
  4. class Event {
  5. constructor(type, target) {
  6. this.target = target;
  7. this.type = type;
  8. }
  9. }
  10. class ErrorEvent extends Event {
  11. constructor(error, target) {
  12. super('error', target);
  13. this.message = error.message;
  14. this.error = error;
  15. }
  16. }
  17. class CloseEvent extends Event {
  18. constructor(code = 1000, reason = '', target) {
  19. super('close', target);
  20. this.wasClean = true;
  21. this.code = code;
  22. this.reason = reason;
  23. }
  24. }
  25. /*!
  26. * Reconnecting WebSocket
  27. * by Pedro Ladaria <pedro.ladaria@gmail.com>
  28. * https://github.com/pladaria/reconnecting-websocket
  29. * License MIT
  30. */
  31. const _setTimeout = RNTimer ? (callback, time) => RNTimer.setTimeout(callback, time) : setTimeout;
  32. const _clearTimeout = RNTimer ? (time) => RNTimer.clearTimeout(time) : clearTimeout;
  33. const getGlobalWebSocket = () => {
  34. if (typeof WebSocket !== 'undefined') {
  35. // @ts-ignore
  36. return WebSocket;
  37. }
  38. };
  39. /**
  40. * Returns true if given argument looks like a WebSocket class
  41. */
  42. const isWebSocket = (w) => typeof w === 'function' && w.CLOSING === 2;
  43. const DEFAULT = {
  44. maxReconnectionDelay: 10000,
  45. minReconnectionDelay: 1000 + Math.random() * 4000,
  46. minUptime: 5000,
  47. reconnectionDelayGrowFactor: 1.3,
  48. connectionTimeout: 4000,
  49. maxRetries: Infinity,
  50. debug: false,
  51. };
  52. class ReconnectingWebSocket {
  53. constructor(url, protocols, options = {}) {
  54. this._listeners = {
  55. error: [],
  56. message: [],
  57. open: [],
  58. close: [],
  59. };
  60. this._retryCount = -1;
  61. this._shouldReconnect = true;
  62. this._connectLock = false;
  63. this._binaryType = 'blob';
  64. this.eventToHandler = new Map([
  65. ['open', this._handleOpen.bind(this)],
  66. ['close', this._handleClose.bind(this)],
  67. ['error', this._handleError.bind(this)],
  68. ['message', this._handleMessage.bind(this)],
  69. ]);
  70. /**
  71. * An event listener to be called when the WebSocket connection's readyState changes to CLOSED
  72. */
  73. this.onclose = undefined;
  74. /**
  75. * An event listener to be called when an error occurs
  76. */
  77. this.onerror = undefined;
  78. /**
  79. * An event listener to be called when a message is received from the server
  80. */
  81. this.onmessage = undefined;
  82. /**
  83. * An event listener to be called when the WebSocket connection's readyState changes to OPEN;
  84. * this indicates that the connection is ready to send and receive data
  85. */
  86. this.onopen = undefined;
  87. this._url = url;
  88. this._protocols = protocols;
  89. this._options = options;
  90. this._connect();
  91. }
  92. static get CONNECTING() {
  93. return 0;
  94. }
  95. static get OPEN() {
  96. return 1;
  97. }
  98. static get CLOSING() {
  99. return 2;
  100. }
  101. static get CLOSED() {
  102. return 3;
  103. }
  104. get CONNECTING() {
  105. return ReconnectingWebSocket.CONNECTING;
  106. }
  107. get OPEN() {
  108. return ReconnectingWebSocket.OPEN;
  109. }
  110. get CLOSING() {
  111. return ReconnectingWebSocket.CLOSING;
  112. }
  113. get CLOSED() {
  114. return ReconnectingWebSocket.CLOSED;
  115. }
  116. get binaryType() {
  117. return this._ws ? this._ws.binaryType : this._binaryType;
  118. }
  119. set binaryType(value) {
  120. this._binaryType = value;
  121. if (this._ws) {
  122. // @ts-ignore
  123. this._ws.binaryType = value;
  124. }
  125. }
  126. /**
  127. * Returns the number or connection retries
  128. */
  129. get retryCount() {
  130. return Math.max(this._retryCount, 0);
  131. }
  132. /**
  133. * The number of bytes of data that have been queued using calls to send() but not yet
  134. * transmitted to the network. This value resets to zero once all queued data has been sent.
  135. * This value does not reset to zero when the connection is closed; if you keep calling send(),
  136. * this will continue to climb. Read only
  137. */
  138. get bufferedAmount() {
  139. return this._ws ? this._ws.bufferedAmount : 0;
  140. }
  141. /**
  142. * The extensions selected by the server. This is currently only the empty string or a list of
  143. * extensions as negotiated by the connection
  144. */
  145. get extensions() {
  146. return this._ws ? this._ws.extensions : '';
  147. }
  148. /**
  149. * A string indicating the name of the sub-protocol the server selected;
  150. * this will be one of the strings specified in the protocols parameter when creating the
  151. * WebSocket object
  152. */
  153. get protocol() {
  154. return this._ws ? this._ws.protocol : '';
  155. }
  156. /**
  157. * The current state of the connection; this is one of the Ready state constants
  158. */
  159. get readyState() {
  160. return this._ws ? this._ws.readyState : ReconnectingWebSocket.CONNECTING;
  161. }
  162. /**
  163. * The URL as resolved by the constructor
  164. */
  165. get url() {
  166. return this._ws ? this._ws.url : '';
  167. }
  168. /**
  169. * Closes the WebSocket connection or connection attempt, if any. If the connection is already
  170. * CLOSED, this method does nothing
  171. */
  172. close(code, reason) {
  173. this._shouldReconnect = false;
  174. if (!this._ws || this._ws.readyState === this.CLOSED) {
  175. return;
  176. }
  177. this._ws.close(code, reason);
  178. }
  179. /**
  180. * Closes the WebSocket connection or connection attempt and connects again.
  181. * Resets retry counter;
  182. */
  183. reconnect(code, reason) {
  184. this._shouldReconnect = true;
  185. this._retryCount = -1;
  186. if (!this._ws || this._ws.readyState === this.CLOSED) {
  187. this._connect();
  188. }
  189. this._disconnect(code, reason);
  190. this._connect();
  191. }
  192. /**
  193. * Enqueues the specified data to be transmitted to the server over the WebSocket connection
  194. */
  195. send(data) {
  196. if (this._ws) {
  197. this._ws.send(data);
  198. }
  199. }
  200. /**
  201. * Register an event handler of a specific event type
  202. */
  203. addEventListener(type, listener) {
  204. if (this._listeners[type]) {
  205. // @ts-ignore
  206. this._listeners[type].push(listener);
  207. }
  208. }
  209. /**
  210. * Removes an event listener
  211. */
  212. removeEventListener(type, listener) {
  213. if (this._listeners[type]) {
  214. // @ts-ignore
  215. this._listeners[type] = this._listeners[type].filter(l => l !== listener);
  216. }
  217. }
  218. _debug(...params) {
  219. if (this._options.debug) {
  220. // tslint:disable-next-line
  221. console.log('RWS>', ...params);
  222. }
  223. }
  224. _getNextDelay() {
  225. let delay = 0;
  226. if (this._retryCount > 0) {
  227. const { reconnectionDelayGrowFactor = DEFAULT.reconnectionDelayGrowFactor, minReconnectionDelay = DEFAULT.minReconnectionDelay, maxReconnectionDelay = DEFAULT.maxReconnectionDelay, } = this._options;
  228. delay =
  229. minReconnectionDelay + Math.pow(this._retryCount - 1, reconnectionDelayGrowFactor);
  230. if (delay > maxReconnectionDelay) {
  231. delay = maxReconnectionDelay;
  232. }
  233. }
  234. this._debug('next delay', delay);
  235. return delay;
  236. }
  237. _wait() {
  238. return new Promise(resolve => {
  239. _setTimeout(resolve, this._getNextDelay());
  240. });
  241. }
  242. /**
  243. * @return Promise<string>
  244. */
  245. _getNextUrl(urlProvider) {
  246. if (typeof urlProvider === 'string') {
  247. return Promise.resolve(urlProvider);
  248. }
  249. if (typeof urlProvider === 'function') {
  250. const url = urlProvider();
  251. if (typeof url === 'string') {
  252. return Promise.resolve(url);
  253. }
  254. if (url.then) {
  255. return url;
  256. }
  257. }
  258. throw Error('Invalid URL');
  259. }
  260. _connect() {
  261. if (this._connectLock) {
  262. return;
  263. }
  264. this._connectLock = true;
  265. const { maxRetries = DEFAULT.maxRetries, connectionTimeout = DEFAULT.connectionTimeout, WebSocket = getGlobalWebSocket(), } = this._options;
  266. if (this._retryCount >= maxRetries) {
  267. this._debug('max retries reached', this._retryCount, '>=', maxRetries);
  268. return;
  269. }
  270. this._retryCount++;
  271. this._debug('connect', this._retryCount);
  272. this._removeListeners();
  273. if (!isWebSocket(WebSocket)) {
  274. throw Error('No valid WebSocket class provided');
  275. }
  276. this._wait()
  277. .then(() => this._getNextUrl(this._url))
  278. .then(url => {
  279. this._debug('connect', { url, protocols: this._protocols });
  280. this._ws = new WebSocket(url, this._protocols);
  281. // @ts-ignore
  282. this._ws.binaryType = this._binaryType;
  283. this._connectLock = false;
  284. this._addListeners();
  285. this._connectTimeout = _setTimeout(() => this._handleTimeout(), connectionTimeout);
  286. });
  287. }
  288. _handleTimeout() {
  289. this._debug('timeout event');
  290. this._handleError(new ErrorEvent(Error('TIMEOUT'), this));
  291. }
  292. _disconnect(code, reason) {
  293. _clearTimeout(this._connectTimeout);
  294. if (!this._ws) {
  295. return;
  296. }
  297. this._removeListeners();
  298. try {
  299. this._ws.close(code, reason);
  300. this._handleClose(new CloseEvent(code, reason, this));
  301. }
  302. catch (error) {
  303. // ignore
  304. }
  305. }
  306. _acceptOpen() {
  307. this._retryCount = 0;
  308. }
  309. _handleOpen(event) {
  310. this._debug('open event');
  311. const { minUptime = DEFAULT.minUptime } = this._options;
  312. _clearTimeout(this._connectTimeout);
  313. this._uptimeTimeout = _setTimeout(() => this._acceptOpen(), minUptime);
  314. this._debug('assign binary type');
  315. // @ts-ignore
  316. this._ws.binaryType = this._binaryType;
  317. if (this.onopen) {
  318. this.onopen(event);
  319. }
  320. this._listeners.open.forEach(listener => listener(event));
  321. }
  322. _handleMessage(event) {
  323. this._debug('message event');
  324. if (this.onmessage) {
  325. this.onmessage(event);
  326. }
  327. this._listeners.message.forEach(listener => listener(event));
  328. }
  329. _handleError(event) {
  330. this._debug('error event', event.message);
  331. this._disconnect(undefined, event.message === 'TIMEOUT' ? 'timeout' : undefined);
  332. if (this.onerror) {
  333. this.onerror(event);
  334. }
  335. this._debug('exec error listeners');
  336. this._listeners.error.forEach(listener => listener(event));
  337. this._connect();
  338. }
  339. _handleClose(event) {
  340. this._debug('close event');
  341. if (this.onclose) {
  342. this.onclose(event);
  343. }
  344. this._listeners.close.forEach(listener => listener(event));
  345. }
  346. /**
  347. * Remove event listeners to WebSocket instance
  348. */
  349. _removeListeners() {
  350. if (!this._ws) {
  351. return;
  352. }
  353. this._debug('removeListeners');
  354. for (const [type, handler] of this.eventToHandler) {
  355. this._ws.removeEventListener(type, handler);
  356. }
  357. }
  358. /**
  359. * Assign event listeners to WebSocket instance
  360. */
  361. _addListeners() {
  362. this._debug('addListeners');
  363. for (const [type, handler] of this.eventToHandler) {
  364. this._ws.addEventListener(type, handler);
  365. }
  366. }
  367. }
  368. module.exports = ReconnectingWebSocket;