Line data Source code
1 : /*
2 : * Famedly Matrix SDK
3 : * Copyright (C) 2020, 2021 Famedly GmbH
4 : *
5 : * This program is free software: you can redistribute it and/or modify
6 : * it under the terms of the GNU Affero General Public License as
7 : * published by the Free Software Foundation, either version 3 of the
8 : * License, or (at your option) any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU Affero General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU Affero General Public License
16 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 : */
18 :
19 : import 'package:matrix/matrix.dart';
20 :
21 : enum UiaRequestState {
22 : /// The request is done
23 : done,
24 :
25 : /// The request has failed
26 : fail,
27 :
28 : /// The request is currently loading
29 : loading,
30 :
31 : /// The request is waiting for user interaction
32 : waitForUser,
33 : }
34 :
35 : /// Wrapper to handle User interactive authentication requests
36 : class UiaRequest<T> {
37 : void Function(UiaRequestState state)? onUpdate;
38 : final Future<T> Function(AuthenticationData? auth) request;
39 : String? session;
40 : UiaRequestState _state = UiaRequestState.loading;
41 : T? result;
42 : Exception? error;
43 : Set<String> nextStages = <String>{};
44 : Map<String, dynamic> params = <String, dynamic>{};
45 :
46 6 : UiaRequestState get state => _state;
47 :
48 3 : set state(UiaRequestState newState) {
49 6 : if (_state == newState) return;
50 3 : _state = newState;
51 6 : onUpdate?.call(newState);
52 : }
53 :
54 3 : UiaRequest({this.onUpdate, required this.request}) {
55 : // ignore: discarded_futures
56 3 : _run();
57 : }
58 :
59 3 : Future<T?> _run([AuthenticationData? auth]) async {
60 3 : state = UiaRequestState.loading;
61 : try {
62 6 : final res = await request(auth);
63 3 : state = UiaRequestState.done;
64 3 : result = res;
65 : return res;
66 2 : } on MatrixException catch (err) {
67 2 : if (err.session == null) {
68 0 : error = err;
69 0 : state = UiaRequestState.fail;
70 : return null;
71 : }
72 4 : session ??= err.session;
73 2 : final completed = err.completedAuthenticationFlows;
74 2 : final flows = err.authenticationFlows ?? <AuthenticationFlow>[];
75 4 : params = err.authenticationParams ?? <String, dynamic>{};
76 4 : nextStages = getNextStages(flows, completed);
77 4 : if (nextStages.isEmpty) {
78 0 : error = err;
79 0 : state = UiaRequestState.fail;
80 : return null;
81 : }
82 : return null;
83 : } catch (err) {
84 4 : error = err is Exception ? err : Exception(err);
85 2 : state = UiaRequestState.fail;
86 : return null;
87 : } finally {
88 6 : if (state == UiaRequestState.loading) {
89 2 : state = UiaRequestState.waitForUser;
90 : }
91 : }
92 : }
93 :
94 4 : Future<T?> completeStage(AuthenticationData auth) => _run(auth);
95 :
96 : /// Cancel this uia request for example if the app can not handle this stage.
97 0 : void cancel([Exception? err]) {
98 0 : error = err ?? Exception('Request has been canceled');
99 0 : state = UiaRequestState.fail;
100 : }
101 :
102 2 : Set<String> getNextStages(
103 : List<AuthenticationFlow> flows,
104 : List<String> completed,
105 : ) {
106 : final nextStages = <String>{};
107 4 : for (final flow in flows) {
108 : // check the flow starts with the completed stages
109 8 : if (flow.stages.length >= completed.length &&
110 10 : flow.stages.take(completed.length).toSet().containsAll(completed)) {
111 6 : final stages = flow.stages.skip(completed.length);
112 6 : if (stages.isNotEmpty) nextStages.add(stages.first);
113 : }
114 : }
115 : return nextStages;
116 : }
117 : }
|