You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ResumeTask.ts 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import { getLogger } from '@jitsi/logger';
  2. import {
  3. NETWORK_INFO_EVENT,
  4. default as NetworkInfo
  5. } from '../connectivity/NetworkInfo';
  6. import { getJitterDelay } from '../util/Retry';
  7. import Strophe from 'strophe.js';
  8. const logger = getLogger('modules/xmpp/ResumeTask');
  9. export interface INetworkInfoEvent {
  10. isOnline: boolean;
  11. }
  12. /**
  13. * The class contains the logic for triggering connection resume via XEP-0198 stream management.
  14. * It does two things, the first one is it tracks the internet online/offline status and it makes sure that
  15. * the reconnect is attempted only while online. The seconds thing is that it tracks the retry attempts and extends
  16. * the retry interval using the full jitter pattern.
  17. */
  18. export default class ResumeTask {
  19. private _stropheConn: Strophe.Connection;
  20. private _resumeRetryN: number;
  21. private _retryDelay: number | undefined;
  22. private _resumeTimeout: NodeJS.Timeout | undefined;
  23. private _networkOnlineListener: (() => void) | null;
  24. /**
  25. * Initializes new {@code RetryTask}.
  26. * @param {Strophe.Connection} stropheConnection - The Strophe connection instance.
  27. */
  28. constructor(stropheConnection: Strophe.Connection) {
  29. this._stropheConn = stropheConnection;
  30. this._resumeRetryN = 0;
  31. this._retryDelay = undefined;
  32. this._resumeTimeout = undefined;
  33. this._networkOnlineListener = null;
  34. }
  35. /**
  36. * @returns {number} - The amount of retries.
  37. */
  38. get retryCount(): number {
  39. return this._resumeRetryN;
  40. }
  41. /**
  42. * @returns {number|undefined} - How much the app will wait before trying to resume the XMPP connection. When
  43. * 'undefined' it means that no resume task was not scheduled.
  44. */
  45. get retryDelay(): number | undefined {
  46. return this._retryDelay;
  47. }
  48. /**
  49. * Called by {@link XmppConnection} when the connection drops and it's a signal it wants to schedule a reconnect.
  50. *
  51. * @returns {void}
  52. */
  53. schedule(): void {
  54. this._cancelResume();
  55. this._removeNetworkOnlineListener();
  56. this._resumeRetryN += 1;
  57. this._networkOnlineListener = NetworkInfo.addCancellableListener(
  58. NETWORK_INFO_EVENT,
  59. ({ isOnline }: INetworkInfoEvent) => {
  60. if (isOnline) {
  61. this._scheduleResume();
  62. } else {
  63. this._cancelResume();
  64. }
  65. }
  66. ) as () => void;
  67. NetworkInfo.isOnline() && this._scheduleResume();
  68. }
  69. /**
  70. * Schedules a delayed timeout which will execute the resume action.
  71. * @private
  72. * @returns {void}
  73. */
  74. private _scheduleResume(): void {
  75. if (this._resumeTimeout) {
  76. // NO-OP
  77. return;
  78. }
  79. // The retry delay will be:
  80. // 1st retry: 1.5s - 3s
  81. // 2nd retry: 3s - 9s
  82. // 3rd and next retry: 4.5s - 27s
  83. this._retryDelay = getJitterDelay(
  84. /* retry */ this._resumeRetryN,
  85. /* minDelay */ this._resumeRetryN * 1500,
  86. 3
  87. );
  88. logger.info(`Will try to resume the XMPP connection in ${this.retryDelay}ms`);
  89. this._resumeTimeout = setTimeout(() => this._resumeConnection(), this.retryDelay);
  90. }
  91. /**
  92. * Cancels the delayed resume task.
  93. *
  94. * @private
  95. * @returns {void}
  96. */
  97. private _cancelResume(): void {
  98. if (this._resumeTimeout) {
  99. logger.info('Canceling connection resume task');
  100. clearTimeout(this._resumeTimeout);
  101. this._resumeTimeout = undefined;
  102. this._retryDelay = undefined;
  103. }
  104. }
  105. /**
  106. * Removes network online listener for the NETWORK_INFO_EVENT event.
  107. *
  108. * @private
  109. * @returns {void}
  110. */
  111. private _removeNetworkOnlineListener(): void {
  112. if (this._networkOnlineListener) {
  113. this._networkOnlineListener();
  114. this._networkOnlineListener = null;
  115. }
  116. }
  117. /**
  118. * Resumes the XMPP connection using the stream management plugin.
  119. *
  120. * @private
  121. * @returns {void}
  122. */
  123. private _resumeConnection(): void {
  124. this._resumeTimeout = undefined;
  125. const { streamManagement } = this._stropheConn;
  126. const resumeToken = streamManagement.getResumeToken();
  127. // Things may have changed since when the task was scheduled
  128. if (!resumeToken) {
  129. return;
  130. }
  131. logger.info('Trying to resume the XMPP connection');
  132. const url = new URL(this._stropheConn.service);
  133. let { search } = url;
  134. const pattern = /(previd=)([\w-]+)/;
  135. const oldToken = search.match(pattern);
  136. // Replace previd if the previd value has changed.
  137. if (oldToken && oldToken.indexOf(resumeToken) === -1) {
  138. search = search.replace(pattern, `$1${resumeToken}`);
  139. // Append previd if it doesn't exist.
  140. } else if (!oldToken) {
  141. search += search.indexOf('?') === -1 ? `?previd=${resumeToken}` : `&previd=${resumeToken}`;
  142. }
  143. url.search = search;
  144. this._stropheConn.service = url.toString();
  145. try {
  146. streamManagement.resume();
  147. } catch (e) {
  148. logger.error('Failed to resume XMPP connection', e);
  149. }
  150. }
  151. /**
  152. * Cancels the retry task. It's called by {@link XmppConnection} when it's no longer interested in reconnecting for
  153. * example when the disconnect method is called.
  154. *
  155. * @returns {void}
  156. */
  157. cancel(): void {
  158. this._cancelResume();
  159. this._removeNetworkOnlineListener();
  160. this._resumeRetryN = 0;
  161. }
  162. }