status.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /**
  2. * This file is part of Threema Web.
  3. *
  4. * Threema Web is free software: you can redistribute it and/or modify it
  5. * under the terms of the GNU Affero General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or (at
  7. * your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Affero General Public License
  15. * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. import {ControllerService} from '../services/controller';
  18. import {StateService} from '../services/state';
  19. import {WebClientService} from '../services/webclient';
  20. import GlobalConnectionState = threema.GlobalConnectionState;
  21. /**
  22. * This controller handles state changes globally.
  23. *
  24. * It also controls auto-reconnecting and the connection status indicator bar.
  25. *
  26. * Status updates should be done through the status service.
  27. */
  28. export class StatusController {
  29. private logTag: string = '[StatusController]';
  30. // State variable
  31. private state = GlobalConnectionState.Error;
  32. // Expanded status bar
  33. public expandStatusBar = false;
  34. private expandStatusBarTimer: ng.IPromise<void> | null = null;
  35. private expandStatusBarTimeout = 3000;
  36. // Reconnect
  37. private reconnectTimeout: ng.IPromise<void>;
  38. // Angular services
  39. private $timeout: ng.ITimeoutService;
  40. private $state: ng.ui.IStateService;
  41. private $log: ng.ILogService;
  42. // Custom services
  43. private stateService: StateService;
  44. private webClientService: WebClientService;
  45. private controllerService: ControllerService;
  46. public static $inject = ['$scope', '$timeout', '$log', '$state', 'StateService',
  47. 'WebClientService', 'ControllerService'];
  48. constructor($scope, $timeout: ng.ITimeoutService, $log: ng.ILogService, $state: ng.ui.IStateService,
  49. stateService: StateService, webClientService: WebClientService,
  50. controllerService: ControllerService) {
  51. // Angular services
  52. this.$timeout = $timeout;
  53. this.$log = $log;
  54. this.$state = $state;
  55. // Custom services
  56. this.stateService = stateService;
  57. this.webClientService = webClientService;
  58. this.controllerService = controllerService;
  59. // Watch state changes
  60. $scope.$watch(
  61. () => stateService.state,
  62. (newValue: threema.GlobalConnectionState, oldValue: threema.GlobalConnectionState) => {
  63. if (oldValue !== newValue) {
  64. this.onStateChange(newValue, oldValue);
  65. }
  66. },
  67. );
  68. }
  69. /**
  70. * Return the prefixed status.
  71. */
  72. public get statusClass(): string {
  73. return 'status-' + this.state;
  74. }
  75. /**
  76. * Handle state changes.
  77. */
  78. private onStateChange(newValue: threema.GlobalConnectionState,
  79. oldValue: threema.GlobalConnectionState): void {
  80. if (newValue === oldValue) {
  81. return;
  82. }
  83. this.state = newValue;
  84. switch (newValue) {
  85. case 'ok':
  86. this.collapseStatusBar();
  87. break;
  88. case 'warning':
  89. if (oldValue === 'ok') {
  90. this.scheduleStatusBar();
  91. }
  92. break;
  93. case 'error':
  94. if (this.stateService.wasConnected) {
  95. if (oldValue === 'ok') {
  96. this.scheduleStatusBar();
  97. }
  98. this.reconnect();
  99. }
  100. break;
  101. default:
  102. this.$log.error(this.logTag, 'Invalid state change: From', oldValue, 'to', newValue);
  103. }
  104. }
  105. /**
  106. * Show full status bar with a certain delay.
  107. */
  108. private scheduleStatusBar(): void {
  109. this.expandStatusBarTimer = this.$timeout(() => {
  110. this.expandStatusBar = true;
  111. }, this.expandStatusBarTimeout);
  112. }
  113. /**
  114. * Collapse the status bar if expanded.
  115. */
  116. private collapseStatusBar(): void {
  117. this.expandStatusBar = false;
  118. if (this.expandStatusBarTimer !== null) {
  119. this.$timeout.cancel(this.expandStatusBarTimer);
  120. }
  121. }
  122. /**
  123. * Attempt to reconnect after a connection loss.
  124. */
  125. private reconnect(): void {
  126. this.$log.warn(this.logTag, 'Connection lost. Attempting to reconnect...');
  127. // Get original keys
  128. const originalKeyStore = this.webClientService.salty.keyStore;
  129. const originalPeerPermanentKeyBytes = this.webClientService.salty.peerPermanentKeyBytes;
  130. // Timeout durations
  131. const TIMEOUT1 = 20 * 1000; // Duration per step for first reconnect
  132. const TIMEOUT2 = 20 * 1000; // Duration per step for second reconnect
  133. // Reconnect state
  134. let reconnectTry: 1 | 2 = 1;
  135. // Handler for failed reconnection attempts
  136. const reconnectionFailed = () => {
  137. // Collapse status bar
  138. this.collapseStatusBar();
  139. // Reset state
  140. this.stateService.reset();
  141. // Redirect to welcome page
  142. this.$state.go('welcome', {
  143. initParams: {
  144. keyStore: originalKeyStore,
  145. peerTrustedKey: originalPeerPermanentKeyBytes,
  146. },
  147. });
  148. };
  149. // Handlers for reconnecting timeout
  150. const reconnect2Timeout = () => {
  151. // Give up
  152. this.$log.error(this.logTag, 'Reconnect timeout 2. Going back to initial loading screen...');
  153. reconnectionFailed();
  154. };
  155. const reconnect1Timeout = () => {
  156. // Could not connect so far.
  157. this.$log.error(this.logTag, 'Reconnect timeout 1. Retrying...');
  158. reconnectTry = 2;
  159. this.reconnectTimeout = this.$timeout(reconnect2Timeout, TIMEOUT2);
  160. doSoftReconnect();
  161. };
  162. // Function to soft-reconnect. Does not reset the loaded data.
  163. const doSoftReconnect = () => {
  164. const deleteStoredData = false;
  165. const resetPush = false;
  166. const redirect = false;
  167. this.webClientService.stop(true, deleteStoredData, resetPush, redirect);
  168. this.webClientService.init(originalKeyStore, originalPeerPermanentKeyBytes, false);
  169. this.webClientService.start().then(
  170. () => {
  171. // Cancel timeout
  172. this.$timeout.cancel(this.reconnectTimeout);
  173. // Hide expanded status bar
  174. this.collapseStatusBar();
  175. },
  176. (error) => {
  177. this.$log.error(this.logTag, 'Error state:', error);
  178. this.$timeout.cancel(this.reconnectTimeout);
  179. reconnectionFailed();
  180. },
  181. (progress: threema.ConnectionBuildupStateChange) => {
  182. if (progress.state === 'peer_handshake' || progress.state === 'loading') {
  183. this.$log.debug(this.logTag, 'Connection buildup advanced, resetting timeout');
  184. // Restart timeout
  185. this.$timeout.cancel(this.reconnectTimeout);
  186. if (reconnectTry === 1) {
  187. this.reconnectTimeout = this.$timeout(reconnect1Timeout, TIMEOUT1);
  188. } else if (reconnectTry === 2) {
  189. this.reconnectTimeout = this.$timeout(reconnect2Timeout, TIMEOUT2);
  190. } else {
  191. throw new Error('Invalid reconnectTry value: ' + reconnectTry);
  192. }
  193. }
  194. },
  195. );
  196. };
  197. // Start timeout
  198. this.reconnectTimeout = this.$timeout(reconnect1Timeout, TIMEOUT1);
  199. // Start reconnecting process
  200. doSoftReconnect();
  201. // TODO: Handle server closing state
  202. }
  203. public wide(): boolean {
  204. return this.controllerService.getControllerName() !== undefined
  205. && this.controllerService.getControllerName() === 'messenger';
  206. }
  207. }