66#import " FLTLocalAuthPlugin.h"
77
88@interface FLTLocalAuthPlugin ()
9- @property (copy , nullable ) NSDictionary <NSString *, NSNumber *> *lastCallArgs;
10- @property (nullable ) FlutterResult lastResult;
9+ @property (nonatomic , copy , nullable ) NSDictionary <NSString *, NSNumber *> *lastCallArgs;
10+ @property (nonatomic , nullable ) FlutterResult lastResult;
11+ // For unit tests to inject dummy LAContext instances that will be used when a new context would
12+ // normally be created. Each call to createAuthContext will remove the current first element from
13+ // the array.
14+ - (void )setAuthContextOverrides : (NSArray <LAContext *> *)authContexts ;
1115@end
1216
13- @implementation FLTLocalAuthPlugin
17+ @implementation FLTLocalAuthPlugin {
18+ NSMutableArray <LAContext *> *_authContextOverrides;
19+ }
1420
1521+ (void )registerWithRegistrar : (NSObject <FlutterPluginRegistrar> *)registrar {
1622 FlutterMethodChannel *channel =
@@ -40,6 +46,19 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
4046
4147#pragma mark Private Methods
4248
49+ - (void )setAuthContextOverrides : (NSArray <LAContext *> *)authContexts {
50+ _authContextOverrides = [authContexts mutableCopy ];
51+ }
52+
53+ - (LAContext *)createAuthContext {
54+ if ([_authContextOverrides count ] > 0 ) {
55+ LAContext *context = [_authContextOverrides firstObject ];
56+ [_authContextOverrides removeObjectAtIndex: 0 ];
57+ return context;
58+ }
59+ return [[LAContext alloc ] init ];
60+ }
61+
4362- (void )alertMessage : (NSString *)message
4463 firstButton : (NSString *)firstButton
4564 flutterResult : (FlutterResult)result
@@ -75,7 +94,7 @@ - (void)alertMessage:(NSString *)message
7594}
7695
7796- (void )getAvailableBiometrics : (FlutterResult)result {
78- LAContext *context = [[LAContext alloc ] init ] ;
97+ LAContext *context = self. createAuthContext ;
7998 NSError *authError = nil ;
8099 NSMutableArray <NSString *> *biometrics = [[NSMutableArray <NSString *> alloc] init ];
81100 if ([context canEvaluatePolicy: LAPolicyDeviceOwnerAuthenticationWithBiometrics
@@ -96,9 +115,10 @@ - (void)getAvailableBiometrics:(FlutterResult)result {
96115 }
97116 result (biometrics);
98117}
118+
99119- (void )authenticateWithBiometrics : (NSDictionary *)arguments
100120 withFlutterResult : (FlutterResult)result {
101- LAContext *context = [[LAContext alloc ] init ] ;
121+ LAContext *context = self. createAuthContext ;
102122 NSError *authError = nil ;
103123 self.lastCallArgs = nil ;
104124 self.lastResult = nil ;
@@ -109,35 +129,20 @@ - (void)authenticateWithBiometrics:(NSDictionary *)arguments
109129 [context evaluatePolicy: LAPolicyDeviceOwnerAuthenticationWithBiometrics
110130 localizedReason: arguments[@" localizedReason" ]
111131 reply: ^(BOOL success, NSError *error) {
112- if (success) {
113- result (@YES );
114- } else {
115- switch (error.code ) {
116- case LAErrorPasscodeNotSet:
117- case LAErrorTouchIDNotAvailable:
118- case LAErrorTouchIDNotEnrolled:
119- case LAErrorTouchIDLockout:
120- [self handleErrors: error
121- flutterArguments: arguments
122- withFlutterResult: result];
123- return ;
124- case LAErrorSystemCancel:
125- if ([arguments[@" stickyAuth" ] boolValue ]) {
126- self.lastCallArgs = arguments;
127- self.lastResult = result;
128- return ;
129- }
130- }
131- result (@NO );
132- }
132+ dispatch_async (dispatch_get_main_queue (), ^{
133+ [self handleAuthReplyWithSuccess: success
134+ error: error
135+ flutterArguments: arguments
136+ flutterResult: result];
137+ });
133138 }];
134139 } else {
135140 [self handleErrors: authError flutterArguments: arguments withFlutterResult: result];
136141 }
137142}
138143
139144- (void )authenticate : (NSDictionary *)arguments withFlutterResult : (FlutterResult)result {
140- LAContext *context = [[LAContext alloc ] init ] ;
145+ LAContext *context = self. createAuthContext ;
141146 NSError *authError = nil ;
142147 _lastCallArgs = nil ;
143148 _lastResult = nil ;
@@ -148,27 +153,12 @@ - (void)authenticate:(NSDictionary *)arguments withFlutterResult:(FlutterResult)
148153 [context evaluatePolicy: kLAPolicyDeviceOwnerAuthentication
149154 localizedReason: arguments[@" localizedReason" ]
150155 reply: ^(BOOL success, NSError *error) {
151- if (success) {
152- result (@YES );
153- } else {
154- switch (error.code ) {
155- case LAErrorPasscodeNotSet:
156- case LAErrorTouchIDNotAvailable:
157- case LAErrorTouchIDNotEnrolled:
158- case LAErrorTouchIDLockout:
159- [self handleErrors: error
160- flutterArguments: arguments
161- withFlutterResult: result];
162- return ;
163- case LAErrorSystemCancel:
164- if ([arguments[@" stickyAuth" ] boolValue ]) {
165- self->_lastCallArgs = arguments;
166- self->_lastResult = result;
167- return ;
168- }
169- }
170- result (@NO );
171- }
156+ dispatch_async (dispatch_get_main_queue (), ^{
157+ [self handleAuthReplyWithSuccess: success
158+ error: error
159+ flutterArguments: arguments
160+ flutterResult: result];
161+ });
172162 }];
173163 } else {
174164 [self handleErrors: authError flutterArguments: arguments withFlutterResult: result];
@@ -178,6 +168,32 @@ - (void)authenticate:(NSDictionary *)arguments withFlutterResult:(FlutterResult)
178168 }
179169}
180170
171+ - (void )handleAuthReplyWithSuccess : (BOOL )success
172+ error : (NSError *)error
173+ flutterArguments : (NSDictionary *)arguments
174+ flutterResult : (FlutterResult)result {
175+ NSAssert ([NSThread isMainThread ], @" Response handling must be done on the main thread." );
176+ if (success) {
177+ result (@YES );
178+ } else {
179+ switch (error.code ) {
180+ case LAErrorPasscodeNotSet:
181+ case LAErrorTouchIDNotAvailable:
182+ case LAErrorTouchIDNotEnrolled:
183+ case LAErrorTouchIDLockout:
184+ [self handleErrors: error flutterArguments: arguments withFlutterResult: result];
185+ return ;
186+ case LAErrorSystemCancel:
187+ if ([arguments[@" stickyAuth" ] boolValue ]) {
188+ self->_lastCallArgs = arguments;
189+ self->_lastResult = result;
190+ return ;
191+ }
192+ }
193+ result (@NO );
194+ }
195+ }
196+
181197- (void )handleErrors : (NSError *)authError
182198 flutterArguments : (NSDictionary *)arguments
183199 withFlutterResult : (FlutterResult)result {
0 commit comments