import { ios as iOSUtils } from "tns-core-modules/utils/utils" ; import { BiometricIDAvailableResult , FingerprintAuthApi , VerifyFingerprintOptions , VerifyFingerprintWithCustomFallbackOptions } from "./fingerprint-auth.common" ; const keychainItemIdentifier = "TouchIDKey" ; let keychainItemServiceName = null ; export class FingerprintAuth implements FingerprintAuthApi { private laContext : LAContext ; available ( ) { return new Promise ( ( resolve , reject ) => { try { const laContext = LAContext . new ( ) ; const hasBio = laContext . canEvaluatePolicyError ( LAPolicy . DeviceOwnerAuthenticationWithBiometrics ) ; resolve ( { any : hasBio , touch : hasBio && laContext . biometryType === 1 , face : hasBio && laContext . biometryType === 2 } ) ; } catch ( ex ) { console . log ( `fingerprint-auth.available: ${ ex }` ) ; resolve ( { any : false } ) ; } } ) ; } didFingerprintDatabaseChange ( ) { return new Promise ( ( resolve , reject ) => { try { const laContext = LAContext . new ( ) ; if ( ! laContext . canEvaluatePolicyError ( LAPolicy . DeviceOwnerAuthenticationWithBiometrics ) ) { reject ( "Not available" ) ; return ; } if ( iOSUtils . MajorVersion < 9 ) { resolve ( false ) ; return ; } const FingerprintDatabaseStateKey = "FingerprintDatabaseStateKey" ; const state = laContext . evaluatedPolicyDomainState ; if ( state !== null ) { const stateStr = state . base64EncodedStringWithOptions ( 0 ) ; const storedState = NSUserDefaults . standardUserDefaults . stringForKey ( FingerprintDatabaseStateKey ) ; NSUserDefaults . standardUserDefaults . setObjectForKey ( stateStr , FingerprintDatabaseStateKey ) ; NSUserDefaults . standardUserDefaults . synchronize ( ) ; const changed = storedState !== null && stateStr !== storedState ; resolve ( changed ) ; } } catch ( ex ) { console . log ( `Error in fingerprint-auth.didFingerprintDatabaseChange: ${ ex }` ) ; resolve ( false ) ; } } ) ; } /**
     * this 'default' method uses keychain instead of localauth so the passcode fallback can be used
     */ verifyFingerprint ( options ) { return new Promise ( ( resolve , reject ) => { try { if ( keychainItemServiceName === null ) { const bundleID = NSBundle . mainBundle . infoDictionary . objectForKey ( "CFBundleIdentifier" ) ; keychainItemServiceName = `${ bundleID }.TouchID` ; } if ( ! FingerprintAuth . createKeyChainEntry ( ) ) { this . verifyFingerprintWithCustomFallback ( options , true ) . then ( resolve , reject ) ; return ; } const query = NSMutableDictionary . alloc ( ) . init ( ) ; query . setObjectForKey ( kSecClassGenericPassword , kSecClass ) ; query . setObjectForKey ( keychainItemIdentifier , kSecAttrAccount ) ; query . setObjectForKey ( keychainItemServiceName , kSecAttrService ) ; query . setObjectForKey ( ( options !== null && options . message ) || "Scan your finger" , kSecUseOperationPrompt ) ; const res = SecItemCopyMatching ( query , null ) ; if ( res === 0 ) { resolve ( ) ; } else { reject ( ) ; } } catch ( ex ) { console . log ( `Error in fingerprint-auth.verifyFingerprint: ${ ex }` ) ; reject ( ex ) ; } } ) ; } /**
     * This implementation uses LocalAuthentication and has no built-in passcode fallback
     */ verifyFingerprintWithCustomFallback ( options , usePasscodeFallback = false ) { return new Promise ( ( resolve , reject ) => { try { this . laContext = LAContext . new ( ) ; if ( ! this . laContext . canEvaluatePolicyError ( LAPolicy . DeviceOwnerAuthenticationWithBiometrics ) ) { reject ( "Not available" ) ; return ; } const message = ( options !== null && options . message ) || "Scan your finger" ; if ( options !== null && options . fallbackMessage ) { this . laContext . localizedFallbackTitle = options . fallbackMessage ; } this . laContext . evaluatePolicyLocalizedReasonReply ( usePasscodeFallback ? LAPolicy . DeviceOwnerAuthentication : LAPolicy . DeviceOwnerAuthenticationWithBiometrics , message , ( ok , error ) => { if ( ok ) { resolve ( ) ; } else { reject ( { code : error . code , message : error . localizedDescription } ) ; } } ) ; } catch ( ex ) { console . log ( `Error in fingerprint-auth.verifyFingerprint: ${ ex }` ) ; reject ( ex ) ; } } ) ; } private static createKeyChainEntry ( ) { const attributes = NSMutableDictionary . new ( ) ; attributes . setObjectForKey ( kSecClassGenericPassword , kSecClass ) ; attributes . setObjectForKey ( keychainItemIdentifier , kSecAttrAccount ) ; attributes . setObjectForKey ( keychainItemServiceName , kSecAttrService ) ; const accessControlRef = SecAccessControlCreateWithFlags ( kCFAllocatorDefault , kSecAttrAccessibleWhenUnlockedThisDeviceOnly , 2 , null ) ; if ( accessControlRef === null ) { console . log ( `Can't store identifier '${ keychainItemIdentifier }' in the KeyChain.` ) ; return false ; } else { attributes . setObjectForKey ( accessControlRef , kSecAttrAccessControl ) ; const content = NSString . stringWithString ( "dummy content" ) ; const nsData = content . dataUsingEncoding ( NSUTF8StringEncoding ) ; attributes . setObjectForKey ( nsData , kSecValueData ) ; SecItemAdd ( attributes , null ) ; return true ; } } close ( ) { if ( this . laContext ) { this . laContext . invalidate ( ) ; } } }