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 : }