reconnecting-websocket-amd.js 13 KB

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