state.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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 {AsyncEvent} from 'ts-events';
  18. import TaskConnectionState = threema.TaskConnectionState;
  19. import GlobalConnectionState = threema.GlobalConnectionState;
  20. import ChosenTask = threema.ChosenTask;
  21. const enum Stage {
  22. Signaling,
  23. Task,
  24. }
  25. export class StateService {
  26. private logTag: string = '[StateService]';
  27. // Angular services
  28. private $log: ng.ILogService;
  29. private $interval: ng.IIntervalService;
  30. // Events
  31. public evtConnectionBuildupStateChange = new AsyncEvent<threema.ConnectionBuildupStateChange>();
  32. public evtGlobalConnectionStateChange = new AsyncEvent<threema.GlobalConnectionStateChange>();
  33. public evtUnreadCountChange = new AsyncEvent<number>();
  34. // Connection states
  35. public signalingConnectionState: saltyrtc.SignalingState;
  36. public taskConnectionState: TaskConnectionState;
  37. // Connection buildup state
  38. public connectionBuildupState: threema.ConnectionBuildupState;
  39. public progress = 0;
  40. private progressInterval: ng.IPromise<any> = null;
  41. public slowConnect = false;
  42. // Global connection state
  43. private stage: Stage;
  44. private _state: threema.GlobalConnectionState;
  45. public attempt: number = 0;
  46. // Unread messages
  47. private _unreadCount: number = 0;
  48. public static $inject = ['$log', '$interval'];
  49. constructor($log: ng.ILogService, $interval: ng.IIntervalService) {
  50. this.$log = $log;
  51. this.$interval = $interval;
  52. this.reset();
  53. }
  54. /**
  55. * Getters and setters for global connection state.
  56. */
  57. public get state(): threema.GlobalConnectionState {
  58. return this._state;
  59. }
  60. public set state(state: threema.GlobalConnectionState) {
  61. const prevState = this._state;
  62. if (prevState === state) {
  63. // No need to dispatch any events
  64. return;
  65. }
  66. this._state = state;
  67. this.evtGlobalConnectionStateChange.post({state: state, prevState: prevState});
  68. }
  69. /**
  70. * Signaling connection state.
  71. */
  72. public updateSignalingConnectionState(state: saltyrtc.SignalingState, chosenTask: ChosenTask): void {
  73. const prevState = this.signalingConnectionState;
  74. this.signalingConnectionState = state;
  75. if (this.stage === Stage.Signaling
  76. || (this.stage === Stage.Task && chosenTask === ChosenTask.RelayedData)) {
  77. this.$log.debug(this.logTag, 'Signaling connection state:', prevState, '=>', state);
  78. switch (state) {
  79. case 'new':
  80. case 'ws-connecting':
  81. case 'server-handshake':
  82. case 'peer-handshake':
  83. this.state = GlobalConnectionState.Warning;
  84. break;
  85. case 'task':
  86. this.stage = Stage.Task;
  87. if (chosenTask === ChosenTask.RelayedData) {
  88. this.updateTaskConnectionState(TaskConnectionState.Connected);
  89. } else {
  90. this.state = GlobalConnectionState.Warning;
  91. }
  92. break;
  93. case 'closing':
  94. case 'closed':
  95. this.state = GlobalConnectionState.Error;
  96. break;
  97. default:
  98. this.$log.warn(this.logTag, 'Ignored signaling connection state change to', state);
  99. }
  100. } else {
  101. this.$log.debug(this.logTag, 'Ignored signaling connection state to "' + state + '"');
  102. }
  103. }
  104. /**
  105. * Task connection state.
  106. */
  107. public updateTaskConnectionState(state: TaskConnectionState): void {
  108. const prevState = this.taskConnectionState;
  109. this.taskConnectionState = state;
  110. if (this.stage === Stage.Task) {
  111. this.$log.debug(this.logTag, 'Task connection state:', prevState, '=>', state);
  112. switch (state) {
  113. case TaskConnectionState.New:
  114. case TaskConnectionState.Connecting:
  115. case TaskConnectionState.Reconnecting:
  116. this.state = GlobalConnectionState.Warning;
  117. break;
  118. case TaskConnectionState.Connected:
  119. this.state = GlobalConnectionState.Ok;
  120. this.attempt = 0;
  121. break;
  122. case TaskConnectionState.Disconnected:
  123. this.state = GlobalConnectionState.Error;
  124. break;
  125. default:
  126. this.$log.warn(this.logTag, 'Ignored task connection state change to "' + state + '"');
  127. }
  128. } else {
  129. this.$log.debug(this.logTag, 'Ignored task connection state change to "' + state + '"');
  130. }
  131. }
  132. /**
  133. * Connection buildup state.
  134. */
  135. public updateConnectionBuildupState(state: threema.ConnectionBuildupState): void {
  136. if (this.connectionBuildupState === state) {
  137. return;
  138. }
  139. const prevState = this.connectionBuildupState;
  140. this.$log.debug(this.logTag, 'Connection buildup state:', prevState, '=>', state);
  141. // Update state
  142. this.connectionBuildupState = state;
  143. this.evtConnectionBuildupStateChange.post({state: state, prevState: prevState});
  144. // Cancel progress interval if present
  145. if (this.progressInterval !== null) {
  146. this.$interval.cancel(this.progressInterval);
  147. this.progressInterval = null;
  148. }
  149. // Reset slow connect state
  150. this.slowConnect = false;
  151. // Update progress
  152. switch (state) {
  153. case 'new':
  154. this.progress = 0;
  155. break;
  156. case 'push':
  157. this.progress = 0;
  158. this.progressInterval = this.$interval(() => {
  159. if (this.progress < 12) {
  160. this.progress += 1;
  161. } else {
  162. this.slowConnect = true;
  163. }
  164. }, 800);
  165. break;
  166. case 'manual_start':
  167. this.progress = 0;
  168. break;
  169. case 'connecting':
  170. this.progress = 13;
  171. break;
  172. case 'waiting':
  173. this.progress = 14;
  174. break;
  175. case 'peer_handshake':
  176. this.progress = 15;
  177. this.progressInterval = this.$interval(() => {
  178. if (this.progress < 40) {
  179. this.progress += 5;
  180. } else if (this.progress < 55) {
  181. this.progress += 3;
  182. } else if (this.progress < 60) {
  183. this.progress += 1;
  184. } else {
  185. this.slowConnect = true;
  186. }
  187. }, 500);
  188. break;
  189. case 'loading':
  190. this.progress = 60;
  191. this.progressInterval = this.$interval(() => {
  192. if (this.progress < 80) {
  193. this.progress += 4;
  194. } else if (this.progress < 90) {
  195. this.progress += 2;
  196. } else if (this.progress < 99) {
  197. this.progress += 1;
  198. } else {
  199. this.slowConnect = true;
  200. }
  201. }, 600);
  202. break;
  203. case 'done':
  204. this.progress = 100;
  205. break;
  206. default:
  207. this.progress = 0;
  208. break;
  209. }
  210. }
  211. /**
  212. * Return whether messages can be submitted to the outgoing queue.
  213. *
  214. * The `startupDone` flag indicates, whether the initial data in the
  215. * webclient service has been loaded or not.
  216. */
  217. public readyToSubmit(chosenTask: ChosenTask, startupDone: boolean): boolean {
  218. switch (chosenTask) {
  219. case ChosenTask.RelayedData:
  220. return true;
  221. case ChosenTask.WebRTC:
  222. default:
  223. return this.state === GlobalConnectionState.Ok && startupDone;
  224. }
  225. }
  226. /**
  227. * Getters and setters for unread messages count.
  228. */
  229. public get unreadCount(): number {
  230. return this._unreadCount;
  231. }
  232. public set unreadCount(count: number) {
  233. if (this._unreadCount === count) {
  234. // No need to dispatch any events
  235. return;
  236. }
  237. this._unreadCount = count;
  238. this.evtUnreadCountChange.post(count);
  239. }
  240. /**
  241. * Reset all states.
  242. */
  243. public reset(connectionBuildupState: threema.ConnectionBuildupState = 'new'): void {
  244. this.$log.debug(this.logTag, 'Reset states');
  245. // Reset state
  246. this.signalingConnectionState = 'new';
  247. this.taskConnectionState = TaskConnectionState.New;
  248. this.stage = Stage.Signaling;
  249. this.state = GlobalConnectionState.Error;
  250. this.connectionBuildupState = connectionBuildupState;
  251. this.progress = 0;
  252. this.unreadCount = 0;
  253. }
  254. }