#import "RNPermissionHandlerFaceID.h" @import LocalAuthentication; @import UIKit; @interface RNPermissionHandlerFaceID() @property (nonatomic, strong) LAContext *laContext; @property (nonatomic, strong) void (^resolve)(RNPermissionStatus status); @property (nonatomic, strong) void (^reject)(NSError *error); @end @implementation RNPermissionHandlerFaceID + (NSArray * _Nonnull)usageDescriptionKeys { return @[@"NSFaceIDUsageDescription"]; } + (NSString * _Nonnull)handlerUniqueId { return @"ios.permission.FACE_ID"; } - (void)checkWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject { if (@available(iOS 11.0.1, *)) { LAContext *context = [LAContext new]; NSError *error; [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]; bool hasFaceID = context.biometryType == LABiometryTypeFaceID; if (!hasFaceID) { return resolve(RNPermissionStatusNotAvailable); } if (error != nil) { if (error.code == LAErrorBiometryNotEnrolled) return resolve(RNPermissionStatusNotAvailable); if (error.code != LAErrorBiometryNotAvailable) return reject(error); if (hasFaceID) return resolve(RNPermissionStatusDenied); return resolve(RNPermissionStatusNotAvailable); } if (![RNPermissions isFlaggedAsRequested:[[self class] handlerUniqueId]]) { return resolve(RNPermissionStatusNotDetermined); } resolve(RNPermissionStatusAuthorized); } else { resolve(RNPermissionStatusNotAvailable); } } - (void)requestWithResolver:(void (^ _Nonnull)(RNPermissionStatus))resolve rejecter:(void (^ _Nonnull)(NSError * _Nonnull))reject { if (@available(iOS 11.0.1, *)) { LAContext *context = [LAContext new]; NSError *error; [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]; bool hasFaceID = context.biometryType == LABiometryTypeFaceID; if (!hasFaceID) { return resolve(RNPermissionStatusNotAvailable); } if (error != nil) { if (error.code == LAErrorBiometryNotEnrolled) return resolve(RNPermissionStatusNotAvailable); if (error.code != LAErrorBiometryNotAvailable) return reject(error); if (hasFaceID) return resolve(RNPermissionStatusDenied); return resolve(RNPermissionStatusNotAvailable); } _resolve = resolve; _reject = reject; _laContext = context; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(UIApplicationDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil]; [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSFaceIDUsageDescription"] reply:^(__unused BOOL success, __unused NSError * _Nullable error) {}]; // Hack to invalidate FaceID verification immediately after being requested [self performSelector:@selector(invalidateContext) withObject:self afterDelay:0.05]; } else { resolve(RNPermissionStatusNotAvailable); } } - (void)invalidateContext { [_laContext invalidate]; } - (void)UIApplicationDidBecomeActiveNotification:(__unused NSNotification *)notification { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; [RNPermissions flagAsRequested:[[self class] handlerUniqueId]]; [self checkWithResolver:_resolve rejecter:_reject]; } @end