diff --git a/examples/api/src-tauri/gen/apple/api_iOS/Info.plist b/examples/api/src-tauri/gen/apple/api_iOS/Info.plist
index f0ac4d62..d37dab96 100644
--- a/examples/api/src-tauri/gen/apple/api_iOS/Info.plist
+++ b/examples/api/src-tauri/gen/apple/api_iOS/Info.plist
@@ -40,5 +40,7 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ NSFaceIDUsageDescription
+ Biometric Test
diff --git a/plugins/biometric/ios/Sources/BiometricPlugin.swift b/plugins/biometric/ios/Sources/BiometricPlugin.swift
index 8ffadd23..ff3113f9 100644
--- a/plugins/biometric/ios/Sources/BiometricPlugin.swift
+++ b/plugins/biometric/ios/Sources/BiometricPlugin.swift
@@ -2,15 +2,142 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
+import LocalAuthentication
import SwiftRs
import Tauri
import UIKit
import WebKit
+class BiometricStatus {
+ let available: Bool
+ let biometryType: LABiometryType
+ let errorReason: String?
+ let errorCode: String?
+
+ init(available: Bool, biometryType: LABiometryType, errorReason: String?, errorCode: String?) {
+ self.available = available
+ self.biometryType = biometryType
+ self.errorReason = errorReason
+ self.errorCode = errorCode
+ }
+}
+
class BiometricPlugin: Plugin {
- @objc public func ping(_ invoke: Invoke) throws {
- let value = invoke.getString("value")
- invoke.resolve(["value": value as Any])
+ let authenticationErrorCodeMap: [Int: String] = [
+ 0: "",
+ LAError.appCancel.rawValue: "appCancel",
+ LAError.authenticationFailed.rawValue: "authenticationFailed",
+ LAError.invalidContext.rawValue: "invalidContext",
+ LAError.notInteractive.rawValue: "notInteractive",
+ LAError.passcodeNotSet.rawValue: "passcodeNotSet",
+ LAError.systemCancel.rawValue: "systemCancel",
+ LAError.userCancel.rawValue: "userCancel",
+ LAError.userFallback.rawValue: "userFallback",
+ LAError.biometryLockout.rawValue: "biometryLockout",
+ LAError.biometryNotAvailable.rawValue: "biometryNotAvailable",
+ LAError.biometryNotEnrolled.rawValue: "biometryNotEnrolled",
+ ]
+
+ var status: BiometricStatus!
+
+ public override func load(webview: WKWebView) {
+ let context = LAContext()
+ var error: NSError?
+ var available = context.canEvaluatePolicy(
+ .deviceOwnerAuthenticationWithBiometrics, error: &error)
+ var reason: String? = nil
+ var errorCode: String? = nil
+
+ if available && context.biometryType == .faceID {
+ let entry = Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String
+
+ if entry == nil {
+ available = false
+ reason = "NSFaceIDUsageDescription is not in the app Info.plist"
+ errorCode = authenticationErrorCodeMap[LAError.biometryNotAvailable.rawValue] ?? ""
+ }
+ } else if !available, let error = error {
+ reason = error.localizedDescription
+ if let failureReason = error.localizedFailureReason {
+ reason = "\(reason ?? ""): \(failureReason)"
+ }
+ errorCode =
+ authenticationErrorCodeMap[error.code] ?? authenticationErrorCodeMap[
+ LAError.biometryNotAvailable.rawValue] ?? ""
+ }
+
+ self.status = BiometricStatus(
+ available: available,
+ biometryType: context.biometryType,
+ errorReason: reason,
+ errorCode: errorCode
+ )
+ }
+
+ @objc func status(_ invoke: Invoke) {
+ if self.status.available {
+ invoke.resolve([
+ "isAvailable": self.status.available,
+ "biometryType": self.status.biometryType.rawValue,
+ ])
+ } else {
+ invoke.resolve([
+ "isAvailable": self.status.available,
+ "biometryType": self.status.biometryType.rawValue,
+ "reason": self.status.errorReason ?? "",
+ "code": self.status.errorCode ?? "",
+ ])
+ }
+ }
+
+ @objc func authenticate(_ invoke: Invoke) {
+ guard self.status.available else {
+ invoke.reject(
+ self.status.errorReason ?? "",
+ self.status.errorCode ?? ""
+ )
+ return
+ }
+
+ guard let reason = invoke.getString("reason"), !reason.isEmpty else {
+ invoke.reject("`reason` is required")
+ return
+ }
+
+ let context = LAContext()
+ context.localizedFallbackTitle = invoke.getString("fallbackTitle")
+ context.localizedCancelTitle = invoke.getString("cancelTitle")
+ context.touchIDAuthenticationAllowableReuseDuration = 0
+
+ let allowDeviceCredential = invoke.getBool("allowDeviceCredential") ?? false
+
+ // force system default fallback title if an empty string is provided (the OS hides the fallback button in this case)
+ if allowDeviceCredential,
+ let fallbackTitle = context.localizedFallbackTitle,
+ fallbackTitle.isEmpty
+ {
+ context.localizedFallbackTitle = nil
+ }
+
+ context.evaluatePolicy(
+ allowDeviceCredential ? .deviceOwnerAuthentication : .deviceOwnerAuthenticationWithBiometrics,
+ localizedReason: reason
+ ) { success, error in
+ if success {
+ invoke.resolve()
+ } else {
+ if let policyError = error as? LAError {
+ let code = self.authenticationErrorCodeMap[policyError.code.rawValue]
+ invoke.reject(policyError.localizedDescription, code)
+ } else {
+ invoke.reject(
+ "Unknown error",
+ self.authenticationErrorCodeMap[LAError.authenticationFailed.rawValue]
+ )
+ }
+ }
+ }
+
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e34286e3..01d263bd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -138,7 +138,7 @@ importers:
version: 3.59.1
unocss:
specifier: ^0.53.1
- version: 0.53.1(postcss@8.4.26)(rollup@3.26.3)(vite@4.4.4)
+ version: 0.53.1(postcss@8.4.31)(rollup@3.26.3)(vite@4.4.4)
vite:
specifier: ^4.3.9
version: 4.4.4
@@ -1370,8 +1370,8 @@ packages:
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false
- /@tauri-apps/cli-darwin-arm64@2.0.0-alpha.14:
- resolution: {integrity: sha512-3K416rvSUt8el/fdPnSnHJOI2j5Os9Kyy17XZp+z3PKRRuo/iJPp9L3w0zFGYsh7C+ylzV4OBUSVTi+e+gO5qA==}
+ /@tauri-apps/cli-darwin-arm64@2.0.0-alpha.15:
+ resolution: {integrity: sha512-PxmXanPZtSLtDJyEoj538//cauKoyc/sExAO0fTwJ+o8y2NZB/qQfpbdMIloQQnusRwh+6RjOr0Zs5Y6nBhO5Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
@@ -1379,8 +1379,8 @@ packages:
dev: true
optional: true
- /@tauri-apps/cli-darwin-x64@2.0.0-alpha.14:
- resolution: {integrity: sha512-aLEUGG8Z0UpTENe4/UG6DU8bnB2e1uxyxYvcmFKrHv+EAtR9nLH14alBxPl2K54YXy3JLR4bKROW15a/sFrX9g==}
+ /@tauri-apps/cli-darwin-x64@2.0.0-alpha.15:
+ resolution: {integrity: sha512-IcJGd6mIwQQ9xQhmkNHWjERJoGYpZEknhWeU8a2MnuosX8c9O/zmKWey4ol2KPrumMdmbh8QZzPyh9986GmnUA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
@@ -1388,8 +1388,8 @@ packages:
dev: true
optional: true
- /@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-alpha.14:
- resolution: {integrity: sha512-Lu7unNvurBccxfHIaUQ0gPgUioTkQBMtWGrqO/auZ/JbjPR1W2eBlRwVNXf+nBWX9HwomPR3YD5yZuZmzxRV2g==}
+ /@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-alpha.15:
+ resolution: {integrity: sha512-zD88WJaEZ49BzgmIgj0RVFF/zKrB+NYHEDwmvt60eehQCPfnMuE5/asj0Gp4YJRZ07jZzDfzMCdTGbwWsLnZEA==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
@@ -1397,8 +1397,8 @@ packages:
dev: true
optional: true
- /@tauri-apps/cli-linux-arm64-gnu@2.0.0-alpha.14:
- resolution: {integrity: sha512-g8HkwKvAsWLLMJzPup7B1BCilYmXKwXdee7sf8QFbaIUSccR8i5pXLK5N/quKw5lmldYgFveEyuW9Qs8RgTYnQ==}
+ /@tauri-apps/cli-linux-arm64-gnu@2.0.0-alpha.15:
+ resolution: {integrity: sha512-rmHIZsEb1RT5Ny4hjeK7LC3MRqWLZBfiKC29DX5UzhJySb9g0UeR2esx1PMX6kuU8DOC0RBr8xpEmoTNMtFJ3Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
@@ -1406,8 +1406,8 @@ packages:
dev: true
optional: true
- /@tauri-apps/cli-linux-arm64-musl@2.0.0-alpha.14:
- resolution: {integrity: sha512-ag4UuX6zg7vmBFWmg9ChyiJI7GTMkc8tjr/qobd3Lg9ddmjnVWwLUHt6v1kYhXiU7iLPD5DYDIjU8x/POc3hSA==}
+ /@tauri-apps/cli-linux-arm64-musl@2.0.0-alpha.15:
+ resolution: {integrity: sha512-l3oix62YRE/vjpdxWq38NwZ61yg1vCcGAdfHaSt+Um/ojZHudekchQx56sEh7IMxsHxEtipxZdNEb1WsyAa5JA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
@@ -1415,8 +1415,8 @@ packages:
dev: true
optional: true
- /@tauri-apps/cli-linux-x64-gnu@2.0.0-alpha.14:
- resolution: {integrity: sha512-+CviROc4fzrGqqyHQXh3uc2dGr/oYr19I8r2k+LJ2CDfmtj7CbNd/oC5oehHbHdw1oGFKuDPudrTGvzdRNygYA==}
+ /@tauri-apps/cli-linux-x64-gnu@2.0.0-alpha.15:
+ resolution: {integrity: sha512-m7jWcyA4URtfvM4ySN1G3mO6gQP0qULawP9henks/bcrx2DU5xFP7WFxUxQhlWEtjwtJOI/NscQfzUEE6igs+Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
@@ -1424,8 +1424,8 @@ packages:
dev: true
optional: true
- /@tauri-apps/cli-linux-x64-musl@2.0.0-alpha.14:
- resolution: {integrity: sha512-aCP51HOAQXgVhyPHXKy627bYVRkNnpCvSU3L03pYV8YDoGo+veeuek5UiW7PlNdwx52B/yC3Jz7Dr3gEbFimfQ==}
+ /@tauri-apps/cli-linux-x64-musl@2.0.0-alpha.15:
+ resolution: {integrity: sha512-Xa4JTnYbebnLAMY7JdNuUDgnv/wWA4a8fbg1288kckq3aRXb+ETTV3Tlr/rnsx1s3TpECmSkXjTIvNecvsqjTA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
@@ -1433,8 +1433,8 @@ packages:
dev: true
optional: true
- /@tauri-apps/cli-win32-arm64-msvc@2.0.0-alpha.14:
- resolution: {integrity: sha512-b6Ei5ERUF0KS1bttM7i6U62GmjIvlgK03XZqvL/KLNvUfqRMu8F7JA1ejSExgTxhEhKSWA768HiTXpXk2GjFFw==}
+ /@tauri-apps/cli-win32-arm64-msvc@2.0.0-alpha.15:
+ resolution: {integrity: sha512-YOKmqenjwQkwBesJ3rYWnJ2renRhPAWIdIoTRhMnDacRk28mMWizsGWLT7ZDbYi7AHMR6jMk0eYgAKKd+uBjFg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
@@ -1442,8 +1442,8 @@ packages:
dev: true
optional: true
- /@tauri-apps/cli-win32-ia32-msvc@2.0.0-alpha.14:
- resolution: {integrity: sha512-TDkvu5pd37bKxZ6N+BqngCNGcefY7aHxyJ3BdBGxF+wRMjEMh70mgEXk8i0uM/aUi/Kl1GQoO6xJfUDlIMPXOA==}
+ /@tauri-apps/cli-win32-ia32-msvc@2.0.0-alpha.15:
+ resolution: {integrity: sha512-Z8yMn6jKSCN8atdWwIzHNoqd+kS684RpgFoZhftcxtqYDXfSgU63KLasKu2Wu12a/7TmXqHGHlEBst9nD2VSLw==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
@@ -1451,8 +1451,8 @@ packages:
dev: true
optional: true
- /@tauri-apps/cli-win32-x64-msvc@2.0.0-alpha.14:
- resolution: {integrity: sha512-9yfoEe2RSykKr5hCifVAL5o0gHXgRCS+Wo+RJjQ9L2+QHY7XPLZYAhj/h8jdcAdRveyIQwat3k7wl+SW87v1eg==}
+ /@tauri-apps/cli-win32-x64-msvc@2.0.0-alpha.15:
+ resolution: {integrity: sha512-fcIXUgI1PKeAj2cp7vvXDssWcXxhauCyvtJPmaCVl5pk+5aJlOSx5TPjv0BRyaIO8l4IPW1IakvTRcDvEAQHRw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -1461,20 +1461,20 @@ packages:
optional: true
/@tauri-apps/cli@2.0.0-alpha.15:
- resolution: {integrity: sha512-4/IQwN5S94D6LTXQrDWbSea0pGb9TTC4BwxHUFmhep4NjFxms161v1zadAUIsq/N2x6WwCBGrsdq9SIkgKv49Q==, tarball: https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.0-alpha.15.tgz}
+ resolution: {integrity: sha512-eMMD5MXJDt/j37IGBP501Ov3lux+mrA1WT4EjTk+Oaw4t8fb8ncb7yvbVZ6qyzVo7WHplIGKRzyV0CyZXZropQ==}
engines: {node: '>= 10'}
hasBin: true
optionalDependencies:
- '@tauri-apps/cli-darwin-arm64': 2.0.0-alpha.14
- '@tauri-apps/cli-darwin-x64': 2.0.0-alpha.14
- '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-alpha.14
- '@tauri-apps/cli-linux-arm64-gnu': 2.0.0-alpha.14
- '@tauri-apps/cli-linux-arm64-musl': 2.0.0-alpha.14
- '@tauri-apps/cli-linux-x64-gnu': 2.0.0-alpha.14
- '@tauri-apps/cli-linux-x64-musl': 2.0.0-alpha.14
- '@tauri-apps/cli-win32-arm64-msvc': 2.0.0-alpha.14
- '@tauri-apps/cli-win32-ia32-msvc': 2.0.0-alpha.14
- '@tauri-apps/cli-win32-x64-msvc': 2.0.0-alpha.14
+ '@tauri-apps/cli-darwin-arm64': 2.0.0-alpha.15
+ '@tauri-apps/cli-darwin-x64': 2.0.0-alpha.15
+ '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-alpha.15
+ '@tauri-apps/cli-linux-arm64-gnu': 2.0.0-alpha.15
+ '@tauri-apps/cli-linux-arm64-musl': 2.0.0-alpha.15
+ '@tauri-apps/cli-linux-x64-gnu': 2.0.0-alpha.15
+ '@tauri-apps/cli-linux-x64-musl': 2.0.0-alpha.15
+ '@tauri-apps/cli-win32-arm64-msvc': 2.0.0-alpha.15
+ '@tauri-apps/cli-win32-ia32-msvc': 2.0.0-alpha.15
+ '@tauri-apps/cli-win32-x64-msvc': 2.0.0-alpha.15
dev: true
/@tauri-apps/toml@2.2.4:
@@ -1775,7 +1775,7 @@ packages:
sirv: 2.0.3
dev: true
- /@unocss/postcss@0.53.1(postcss@8.4.26):
+ /@unocss/postcss@0.53.1(postcss@8.4.31):
resolution: {integrity: sha512-vuUj/Tsvn6/YlEYp/AezyjoZLNBp+YomwpQctNZAC5ged5cqKfaw+oISw1LYzi/48Ynx7cV/4XqikApuozrvRQ==}
engines: {node: '>=14'}
peerDependencies:
@@ -1786,7 +1786,7 @@ packages:
css-tree: 2.3.1
fast-glob: 3.3.0
magic-string: 0.30.1
- postcss: 8.4.26
+ postcss: 8.4.31
dev: true
/@unocss/preset-attributify@0.53.1:
@@ -2185,7 +2185,7 @@ packages:
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
- fsevents: 2.3.2
+ fsevents: 2.3.3
dev: true
/cidr-regex@4.0.3:
@@ -3043,8 +3043,8 @@ packages:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true
- /fsevents@2.3.2:
- resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ /fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
@@ -4103,6 +4103,15 @@ packages:
source-map-js: 1.0.2
dev: true
+ /postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.6
+ picocolors: 1.0.0
+ source-map-js: 1.0.2
+ dev: true
+
/prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -4236,7 +4245,7 @@ packages:
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true
optionalDependencies:
- fsevents: 2.3.2
+ fsevents: 2.3.3
dev: true
/run-async@2.4.1:
@@ -4841,7 +4850,7 @@ packages:
'@types/unist': 2.0.7
dev: true
- /unocss@0.53.1(postcss@8.4.26)(rollup@3.26.3)(vite@4.4.4):
+ /unocss@0.53.1(postcss@8.4.31)(rollup@3.26.3)(vite@4.4.4):
resolution: {integrity: sha512-0lRblA8hX7VUu5dywbcStzm590Iz5ahSJGsMNKNH3+u9C7AfJcKT8epxjkIkJWQBNJLD5vsao4SuuhLWB7eMQQ==}
engines: {node: '>=14'}
peerDependencies:
@@ -4854,7 +4863,7 @@ packages:
'@unocss/cli': 0.53.1(rollup@3.26.3)
'@unocss/core': 0.53.1
'@unocss/extractor-arbitrary-variants': 0.53.1
- '@unocss/postcss': 0.53.1(postcss@8.4.26)
+ '@unocss/postcss': 0.53.1(postcss@8.4.31)
'@unocss/preset-attributify': 0.53.1
'@unocss/preset-icons': 0.53.1
'@unocss/preset-mini': 0.53.1
@@ -4932,7 +4941,7 @@ packages:
postcss: 8.4.26
rollup: 3.26.3
optionalDependencies:
- fsevents: 2.3.2
+ fsevents: 2.3.3
dev: true
/vite@4.4.4:
@@ -4967,7 +4976,7 @@ packages:
postcss: 8.4.26
rollup: 3.26.3
optionalDependencies:
- fsevents: 2.3.2
+ fsevents: 2.3.3
dev: true
/vitefu@0.2.4(vite@4.4.4):