diff --git a/.changeset/expo-native-remove-biometric-bridge.md b/.changeset/expo-native-remove-biometric-bridge.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/expo-native-remove-biometric-bridge.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt b/packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt index 93c1030fcb5..a0f870a1a4f 100644 --- a/packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt +++ b/packages/expo-native-components/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt @@ -15,11 +15,7 @@ import com.clerk.api.network.model.client.Client import com.clerk.api.network.model.error.ClerkErrorResponse import com.clerk.api.network.model.error.firstMessage import com.clerk.api.network.serialization.ClerkResult -import com.clerk.api.signin.SignIn -import com.clerk.api.biometriccredential.BiometricCredential -import com.clerk.api.biometriccredential.BiometricCredentialAvailability import com.clerk.api.biometriccredential.BiometricCredentialKeyManagerException -import com.clerk.api.biometriccredential.BiometricCredentialPolicy import com.clerk.api.session.SessionVerification import com.clerk.api.session.startVerification import com.clerk.api.session.verifyWithBiometrics @@ -53,61 +49,6 @@ private fun debugLog(tag: String, message: String) { } } -internal fun biometricCredentialAvailabilityPayload( - availability: BiometricCredentialAvailability -): Map { - return mapOf( - "isAvailable" to availability.isAvailable, - "unavailableReason" to availability.unavailableReason?.name?.lowercase() - ) -} - -internal fun biometricCredentialEnvironmentAvailabilityPayload( - isInitialized: Boolean -): Map? { - if (isInitialized) { - return null - } - - return mapOf( - "isAvailable" to false, - "unavailableReason" to "environment_unavailable" - ) -} - -internal fun biometricCredentialPayload(biometricCredential: BiometricCredential): Map { - return mapOf( - "id" to biometricCredential.id, - "object" to "trusted_device", - "platform" to biometricCredential.platform.name.lowercase(), - "appIdentifier" to biometricCredential.appIdentifier, - "name" to biometricCredential.name, - "algorithm" to biometricCredential.algorithm, - "status" to biometricCredential.status.name.lowercase(), - "createdAt" to biometricCredential.createdAt, - "updatedAt" to biometricCredential.updatedAt, - "lastUsedAt" to biometricCredential.lastUsedAt, - "revokedAt" to biometricCredential.revokedAt - ) -} - -internal fun biometricSignInPayload(signIn: SignIn): Map { - return mapOf( - "id" to signIn.id, - "status" to signIn.status.name.lowercase(), - "createdSessionId" to signIn.createdSessionId - ) -} - -internal fun biometricCredentialPolicy(policy: String): BiometricCredentialPolicy? { - return when (policy) { - "biometry_current_set" -> BiometricCredentialPolicy.BIOMETRY_CURRENT_SET - "biometry_any" -> BiometricCredentialPolicy.BIOMETRY_ANY - "biometry_or_device_passcode" -> BiometricCredentialPolicy.BIOMETRY_OR_DEVICE_PASSCODE - else -> null - } -} - internal fun biometricReverificationLevel(level: String): SessionVerification.Level? = when (level) { "first_factor" -> SessionVerification.Level.FIRST_FACTOR "second_factor" -> SessionVerification.Level.SECOND_FACTOR @@ -250,38 +191,6 @@ class ClerkExpoModule : Module() { promise.resolve(authFlowStatePayload()) } - AsyncFunction("getTrustedDeviceAvailability") { - id: String?, - identifierHint: String?, - promise: Promise -> - getBiometricCredentialAvailability(id, identifierHint, promise) - } - - AsyncFunction("listTrustedDevices") { promise: Promise -> - listBiometricCredentials(promise) - } - - AsyncFunction("enrollTrustedDevice") { - deviceName: String?, - identifierHint: String?, - reason: String?, - policy: String, - promise: Promise -> - enrollBiometricCredential(deviceName, identifierHint, reason, policy, promise) - } - - AsyncFunction("revokeTrustedDevice") { id: String, promise: Promise -> - revokeBiometricCredential(id, promise) - } - - AsyncFunction("signInWithTrustedDevice") { - id: String?, - identifierHint: String?, - reason: String?, - promise: Promise -> - signInWithBiometrics(id, identifierHint, reason, promise) - } - AsyncFunction("reverifyWithBiometrics") { sessionId: String, level: String, @@ -481,181 +390,7 @@ class ClerkExpoModule : Module() { } } - // MARK: - biometric credentials - - private fun getBiometricCredentialAvailability( - id: String?, - identifierHint: String?, - promise: Promise - ) { - val environmentAvailability = biometricCredentialEnvironmentAvailabilityPayload(Clerk.isInitialized.value) - if (environmentAvailability != null) { - promise.resolve(environmentAvailability) - return - } - - coroutineScope.launch { - try { - val availability = Clerk.biometricCredentials.availability(id, identifierHint) - promise.resolve(biometricCredentialAvailabilityPayload(availability)) - } catch (e: Exception) { - rejectBiometricCredentialException( - promise = promise, - fallbackCode = "E_TRUSTED_DEVICE_AVAILABILITY_FAILED", - fallbackMessage = "Unable to determine biometric-credential availability", - exception = e - ) - } - } - } - - private fun listBiometricCredentials(promise: Promise) { - if (!requireBiometricCredentialEnvironment(promise)) { - return - } - - coroutineScope.launch { - try { - when (val result = Clerk.biometricCredentials.list()) { - is ClerkResult.Success -> promise.resolve(result.value.map(::biometricCredentialPayload)) - is ClerkResult.Failure -> rejectBiometricCredentialFailure( - promise, - "E_TRUSTED_DEVICE_LIST_FAILED", - "Unable to list biometric credentials", - result - ) - } - } catch (e: Exception) { - rejectBiometricCredentialException( - promise = promise, - fallbackCode = "E_TRUSTED_DEVICE_LIST_FAILED", - fallbackMessage = "Unable to list biometric credentials", - exception = e - ) - } - } - } - - private fun enrollBiometricCredential( - deviceName: String?, - identifierHint: String?, - reason: String?, - policy: String, - promise: Promise - ) { - if (!requireBiometricCredentialEnvironment(promise)) { - return - } - - val biometricCredentialPolicy = biometricCredentialPolicy(policy) - if (biometricCredentialPolicy == null) { - promise.reject( - "invalid_trusted_device_policy", - "Invalid biometric-credential policy: $policy", - null - ) - return - } - - coroutineScope.launch { - try { - if (!attachCurrentActivityForBiometricCredential(promise)) { - return@launch - } - when ( - val result = Clerk.biometricCredentials.enroll( - name = deviceName, - identifierHint = identifierHint, - policy = biometricCredentialPolicy, - promptSubtitle = reason - ) - ) { - is ClerkResult.Success -> promise.resolve(biometricCredentialPayload(result.value)) - is ClerkResult.Failure -> rejectBiometricCredentialFailure( - promise, - "E_TRUSTED_DEVICE_ENROLLMENT_FAILED", - "Unable to enroll biometric credential", - result - ) - } - } catch (e: Exception) { - rejectBiometricCredentialException( - promise = promise, - fallbackCode = "E_TRUSTED_DEVICE_ENROLLMENT_FAILED", - fallbackMessage = "Unable to enroll biometric credential", - exception = e - ) - } - } - } - - private fun revokeBiometricCredential(id: String, promise: Promise) { - if (!requireBiometricCredentialEnvironment(promise)) { - return - } - - coroutineScope.launch { - try { - when (val result = Clerk.biometricCredentials.revoke(id)) { - is ClerkResult.Success -> promise.resolve(biometricCredentialPayload(result.value)) - is ClerkResult.Failure -> rejectBiometricCredentialFailure( - promise, - "E_TRUSTED_DEVICE_REVOCATION_FAILED", - "Unable to revoke biometric credential", - result - ) - } - } catch (e: Exception) { - rejectBiometricCredentialException( - promise = promise, - fallbackCode = "E_TRUSTED_DEVICE_REVOCATION_FAILED", - fallbackMessage = "Unable to revoke biometric credential", - exception = e - ) - } - } - } - - private fun signInWithBiometrics( - id: String?, - identifierHint: String?, - reason: String?, - promise: Promise - ) { - if (!requireBiometricCredentialEnvironment(promise)) { - return - } - - coroutineScope.launch { - try { - if (!attachCurrentActivityForBiometricCredential(promise)) { - return@launch - } - when ( - val result = Clerk.biometricCredentials.signIn( - id = id, - identifierHint = identifierHint, - promptSubtitle = reason - ) - ) { - is ClerkResult.Success -> promise.resolve(biometricSignInPayload(result.value)) - is ClerkResult.Failure -> rejectBiometricCredentialFailure( - promise, - "E_TRUSTED_DEVICE_SIGN_IN_FAILED", - "Unable to sign in with biometric credential", - result - ) - } - } catch (e: Exception) { - rejectBiometricCredentialException( - promise = promise, - fallbackCode = "E_TRUSTED_DEVICE_SIGN_IN_FAILED", - fallbackMessage = "Unable to sign in with biometric credential", - exception = e - ) - } - } - } + // MARK: - biometric reverification private fun reverifyWithBiometrics( sessionId: String, diff --git a/packages/expo-native-components/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt b/packages/expo-native-components/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt index 525651e5510..c22fcc7bff9 100644 --- a/packages/expo-native-components/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt +++ b/packages/expo-native-components/android/src/test/java/expo/modules/clerk/BiometricCredentialBridgeTest.kt @@ -3,11 +3,7 @@ package expo.modules.clerk import com.clerk.api.network.model.error.ClerkErrorResponse import com.clerk.api.network.model.error.Error as ClerkAPIError import com.clerk.api.network.serialization.ClerkResult -import com.clerk.api.signin.SignIn -import com.clerk.api.biometriccredential.BiometricCredential -import com.clerk.api.biometriccredential.BiometricCredentialAvailability import com.clerk.api.biometriccredential.BiometricCredentialKeyManagerException -import com.clerk.api.biometriccredential.BiometricCredentialPolicy import com.clerk.api.session.SessionVerification import kotlinx.coroutines.cancel import kotlinx.coroutines.currentCoroutineContext @@ -164,92 +160,6 @@ class BiometricCredentialBridgeTest { assertNull(biometricCredentialEnvironmentError(isInitialized = true)) } - @Test - fun `reports unavailable biometric credentials before Clerk initialization`() { - assertEquals( - mapOf( - "isAvailable" to false, - "unavailableReason" to "environment_unavailable" - ), - biometricCredentialEnvironmentAvailabilityPayload(isInitialized = false) - ) - assertNull(biometricCredentialEnvironmentAvailabilityPayload(isInitialized = true)) - } - - @Test - fun `maps biometric-credential availability to the JavaScript contract`() { - assertEquals( - mapOf("isAvailable" to true, "unavailableReason" to null), - biometricCredentialAvailabilityPayload(BiometricCredentialAvailability.Available) - ) - assertEquals( - mapOf( - "isAvailable" to false, - "unavailableReason" to "biometric_authentication_unavailable" - ), - biometricCredentialAvailabilityPayload( - BiometricCredentialAvailability.Unavailable( - BiometricCredentialAvailability.UnavailableReason.BIOMETRIC_AUTHENTICATION_UNAVAILABLE - ) - ) - ) - } - - @Test - fun `maps biometric-credential resources to the JavaScript contract`() { - val payload = biometricCredentialPayload( - BiometricCredential( - id = "td_123", - platform = BiometricCredential.Platform.ANDROID, - appIdentifier = "com.example.app", - name = "Pixel", - status = BiometricCredential.Status.ACTIVE, - createdAt = 1_700_000_000_000, - updatedAt = 1_700_000_100_000, - lastUsedAt = 1_700_000_200_000 - ) - ) - - assertEquals("trusted_device", payload["object"]) - assertEquals("android", payload["platform"]) - assertEquals("active", payload["status"]) - assertEquals("ES256", payload["algorithm"]) - assertEquals(1_700_000_200_000, payload["lastUsedAt"]) - assertNull(payload["revokedAt"]) - } - - @Test - fun `maps every supported authentication policy`() { - assertEquals( - BiometricCredentialPolicy.BIOMETRY_CURRENT_SET, - biometricCredentialPolicy("biometry_current_set") - ) - assertEquals(BiometricCredentialPolicy.BIOMETRY_ANY, biometricCredentialPolicy("biometry_any")) - assertEquals( - BiometricCredentialPolicy.BIOMETRY_OR_DEVICE_PASSCODE, - biometricCredentialPolicy("biometry_or_device_passcode") - ) - assertNull(biometricCredentialPolicy("unsupported")) - } - - @Test - fun `maps biometric sign-in results`() { - assertEquals( - mapOf( - "id" to "sia_123", - "status" to "complete", - "createdSessionId" to "sess_123" - ), - biometricSignInPayload( - SignIn( - id = "sia_123", - status = SignIn.Status.COMPLETE, - createdSessionId = "sess_123" - ) - ) - ) - } - @Test fun `preserves Clerk API error codes and detailed messages`() { val failure = ClerkResult.apiFailure( @@ -271,8 +181,8 @@ class BiometricCredentialBridgeTest { ), biometricCredentialBridgeError( failure = failure, - fallbackCode = "E_TRUSTED_DEVICE_SIGN_IN_FAILED", - fallbackMessage = "Unable to sign in with biometric credential" + fallbackCode = "E_BIOMETRIC_REVERIFICATION_FAILED", + fallbackMessage = "Unable to reverify with biometrics" ) ) } @@ -289,8 +199,8 @@ class BiometricCredentialBridgeTest { BiometricCredentialKeyManagerException.Code.KEY_INVALIDATED, "The biometric credential key was invalidated." ), - fallbackCode = "E_TRUSTED_DEVICE_SIGN_IN_FAILED", - fallbackMessage = "Unable to sign in with biometric credential" + fallbackCode = "E_BIOMETRIC_REVERIFICATION_FAILED", + fallbackMessage = "Unable to reverify with biometrics" ) ) } @@ -299,13 +209,13 @@ class BiometricCredentialBridgeTest { fun `uses fallback details for plain bridge exceptions`() { assertEquals( BiometricCredentialBridgeError( - code = "E_TRUSTED_DEVICE_SIGN_IN_FAILED", - message = "Unable to sign in with biometric credential" + code = "E_BIOMETRIC_REVERIFICATION_FAILED", + message = "Unable to reverify with biometrics" ), biometricCredentialBridgeError( throwable = Exception(), - fallbackCode = "E_TRUSTED_DEVICE_SIGN_IN_FAILED", - fallbackMessage = "Unable to sign in with biometric credential" + fallbackCode = "E_BIOMETRIC_REVERIFICATION_FAILED", + fallbackMessage = "Unable to reverify with biometrics" ) ) } diff --git a/packages/expo-native-components/ios/ClerkExpoModule.swift b/packages/expo-native-components/ios/ClerkExpoModule.swift index 411bf2c7204..0e184362417 100644 --- a/packages/expo-native-components/ios/ClerkExpoModule.swift +++ b/packages/expo-native-components/ios/ClerkExpoModule.swift @@ -85,44 +85,6 @@ public class ClerkExpoModule: Module { self.getAuthFlowState(promise: promise) } - AsyncFunction("getTrustedDeviceAvailability") { - (id: String?, identifierHint: String?, promise: Promise) in - self.getBiometricCredentialAvailability(id: id, identifierHint: identifierHint, promise: promise) - } - - AsyncFunction("listTrustedDevices") { (promise: Promise) in - self.listBiometricCredentials(promise: promise) - } - - AsyncFunction("enrollTrustedDevice") { - (deviceName: String?, - identifierHint: String?, - reason: String?, - policy: String, - promise: Promise) in - self.enrollBiometricCredential( - deviceName: deviceName, - identifierHint: identifierHint, - reason: reason, - policy: policy, - promise: promise - ) - } - - AsyncFunction("revokeTrustedDevice") { (id: String, promise: Promise) in - self.revokeBiometricCredential(id: id, promise: promise) - } - - AsyncFunction("signInWithTrustedDevice") { - (id: String?, identifierHint: String?, reason: String?, promise: Promise) in - self.signInWithBiometrics( - id: id, - identifierHint: identifierHint, - reason: reason, - promise: promise - ) - } - AsyncFunction("reverifyWithBiometrics") { (sessionId: String, level: String, reason: String?, promise: Promise) in Task { @MainActor in @@ -153,105 +115,7 @@ public class ClerkExpoModule: Module { } } - // MARK: - Biometric credentials - - private func getBiometricCredentialAvailability(id: String?, identifierHint: String?, promise: Promise) { - Task { @MainActor in - do { - let availability = try await ClerkNativeBridge.shared.getBiometricCredentialAvailability( - id: id, - identifierHint: identifierHint - ) - promise.resolve(availability) - } catch { - rejectBiometricCredentialError( - error, - fallbackCode: "E_TRUSTED_DEVICE_AVAILABILITY_FAILED", - promise: promise - ) - } - } - } - - private func listBiometricCredentials(promise: Promise) { - Task { @MainActor in - do { - let biometricCredentials = try await ClerkNativeBridge.shared.listBiometricCredentials() - promise.resolve(biometricCredentials) - } catch { - rejectBiometricCredentialError( - error, - fallbackCode: "E_TRUSTED_DEVICE_LIST_FAILED", - promise: promise - ) - } - } - } - - private func enrollBiometricCredential( - deviceName: String?, - identifierHint: String?, - reason: String?, - policy: String, - promise: Promise - ) { - Task { @MainActor in - do { - let biometricCredential = try await ClerkNativeBridge.shared.enrollBiometricCredential( - deviceName: deviceName, - identifierHint: identifierHint, - reason: reason, - policy: policy - ) - promise.resolve(biometricCredential) - } catch { - rejectBiometricCredentialError( - error, - fallbackCode: "E_TRUSTED_DEVICE_ENROLLMENT_FAILED", - promise: promise - ) - } - } - } - - private func revokeBiometricCredential(id: String, promise: Promise) { - Task { @MainActor in - do { - let biometricCredential = try await ClerkNativeBridge.shared.revokeBiometricCredential(id: id) - promise.resolve(biometricCredential) - } catch { - rejectBiometricCredentialError( - error, - fallbackCode: "E_TRUSTED_DEVICE_REVOCATION_FAILED", - promise: promise - ) - } - } - } - - private func signInWithBiometrics( - id: String?, - identifierHint: String?, - reason: String?, - promise: Promise - ) { - Task { @MainActor in - do { - let signIn = try await ClerkNativeBridge.shared.signInWithBiometrics( - id: id, - identifierHint: identifierHint, - reason: reason - ) - promise.resolve(signIn) - } catch { - rejectBiometricCredentialError( - error, - fallbackCode: "E_TRUSTED_DEVICE_SIGN_IN_FAILED", - promise: promise - ) - } - } - } + // MARK: - Biometric reverification private func rejectBiometricCredentialError( _ error: Error, diff --git a/packages/expo-native-components/ios/ClerkNativeBridge.swift b/packages/expo-native-components/ios/ClerkNativeBridge.swift index 5c0ccbc5d19..5933212b703 100644 --- a/packages/expo-native-components/ios/ClerkNativeBridge.swift +++ b/packages/expo-native-components/ios/ClerkNativeBridge.swift @@ -685,87 +685,7 @@ final class ClerkNativeBridge { Self.authFlowStatePayload(Self.authFlowStateSnapshot()) } - // MARK: - Biometric credentials - - @MainActor - func getBiometricCredentialAvailability(id: String?, identifierHint: String?) async throws -> [String: Any] { - guard Self.clerkConfigured else { - return [ - "isAvailable": false, - "unavailableReason": "environment_unavailable", - ] - } - - let availability = try await Clerk.shared.biometricCredentials.availability( - id: id, - identifierHint: identifierHint - ) - - return [ - "isAvailable": availability.isAvailable, - "unavailableReason": availability.unavailableReason - .map(Self.biometricCredentialUnavailableReason) ?? NSNull(), - ] - } - - @MainActor - func listBiometricCredentials() async throws -> [[String: Any]] { - try Self.requireBiometricCredentialEnvironment() - let biometricCredentials = try await Clerk.shared.biometricCredentials.list() - return biometricCredentials.map(Self.biometricCredentialPayload) - } - - @MainActor - func enrollBiometricCredential( - deviceName: String?, - identifierHint: String?, - reason: String?, - policy: String - ) async throws -> [String: Any] { - try Self.requireBiometricCredentialEnvironment() - - guard let biometricCredentialPolicy = BiometricCredentialPolicy(rawValue: policy) else { - throw ClerkExpoBiometricCredentialError( - code: "invalid_trusted_device_policy", - message: "Invalid biometric-credential policy: \(policy)." - ) - } - - let biometricCredential = try await Clerk.shared.biometricCredentials.enroll( - name: deviceName, - identifierHint: identifierHint, - reason: reason, - policy: biometricCredentialPolicy - ) - return Self.biometricCredentialPayload(biometricCredential) - } - - @MainActor - func revokeBiometricCredential(id: String) async throws -> [String: Any] { - try Self.requireBiometricCredentialEnvironment() - let biometricCredential = try await Clerk.shared.biometricCredentials.revoke(id: id) - return Self.biometricCredentialPayload(biometricCredential) - } - - @MainActor - func signInWithBiometrics( - id: String?, - identifierHint: String?, - reason: String? - ) async throws -> [String: Any] { - try Self.requireBiometricCredentialEnvironment() - let signIn = try await Clerk.shared.auth.signInWithBiometrics( - id: id, - identifierHint: identifierHint, - reason: reason - ) - - return [ - "id": signIn.id, - "status": signIn.status.rawValue, - "createdSessionId": Self.bridgeValue(signIn.createdSessionId), - ] - } + // MARK: - Biometric reverification @MainActor private static func requireBiometricCredentialEnvironment() throws { @@ -842,28 +762,6 @@ final class ClerkNativeBridge { ] } - private static func biometricCredentialPayload(_ biometricCredential: BiometricCredential) -> [String: Any] { - [ - "id": biometricCredential.id, - "object": biometricCredential.object, - "platform": biometricCredential.platform.rawValue, - "appIdentifier": biometricCredential.appIdentifier, - "name": bridgeValue(biometricCredential.name), - "algorithm": biometricCredential.algorithm.rawValue, - "status": biometricCredential.status.rawValue, - "createdAt": millisecondsSince1970(biometricCredential.createdAt), - "updatedAt": millisecondsSince1970(biometricCredential.updatedAt), - "lastUsedAt": optionalMillisecondsSince1970(biometricCredential.lastUsedAt), - "revokedAt": optionalMillisecondsSince1970(biometricCredential.revokedAt), - ] - } - - private static func biometricCredentialUnavailableReason( - _ reason: BiometricCredentialAvailability.UnavailableReason - ) -> String { - snakeCase(reason.rawValue) - } - static func biometricCredentialErrorDescriptor( _ error: Error, fallbackCode: String @@ -921,30 +819,6 @@ final class ClerkNativeBridge { } } - private static func snakeCase(_ value: String) -> String { - value - .replacingOccurrences( - of: "([A-Z]+)([A-Z][a-z])", - with: "$1_$2", - options: .regularExpression - ) - .replacingOccurrences( - of: "([a-z0-9])([A-Z])", - with: "$1_$2", - options: .regularExpression - ) - .lowercased() - } - - private static func millisecondsSince1970(_ date: Date) -> Double { - date.timeIntervalSince1970 * 1_000 - } - - private static func optionalMillisecondsSince1970(_ date: Date?) -> Any { - guard let date else { return NSNull() } - return millisecondsSince1970(date) - } - private static func bridgeValue(_ value: Value?) -> Any { guard let value else { return NSNull() } return value diff --git a/packages/expo-native-components/ios/Tests/ClerkNativeBridgeTests.swift b/packages/expo-native-components/ios/Tests/ClerkNativeBridgeTests.swift index ab9fe8b6862..e202ae7f179 100644 --- a/packages/expo-native-components/ios/Tests/ClerkNativeBridgeTests.swift +++ b/packages/expo-native-components/ios/Tests/ClerkNativeBridgeTests.swift @@ -4,43 +4,18 @@ import ClerkKit final class ClerkNativeBridgeTests: XCTestCase { @MainActor - func testBiometricCredentialAvailabilityIsUnavailableBeforeConfiguration() async throws { - let availability = try await ClerkNativeBridge.shared.getBiometricCredentialAvailability( - id: nil, - identifierHint: nil - ) - - XCTAssertEqual(availability["isAvailable"] as? Bool, false) - XCTAssertEqual(availability["unavailableReason"] as? String, "environment_unavailable") - } - - @MainActor - func testBiometricCredentialOperationsRejectBeforeConfiguration() async { - await assertEnvironmentUnavailable { - try await ClerkNativeBridge.shared.reverifyWithBiometrics( + func testBiometricReverificationRejectsBeforeConfiguration() async { + do { + _ = try await ClerkNativeBridge.shared.reverifyWithBiometrics( sessionId: "sess_test", level: "multi_factor", reason: nil ) - } - await assertEnvironmentUnavailable { - try await ClerkNativeBridge.shared.listBiometricCredentials() - } - await assertEnvironmentUnavailable { - try await ClerkNativeBridge.shared.enrollBiometricCredential( - deviceName: nil, - identifierHint: nil, - reason: nil, - policy: "biometry_or_device_passcode" - ) - } - await assertEnvironmentUnavailable { - try await ClerkNativeBridge.shared.revokeBiometricCredential(id: "td_test") - } - await assertEnvironmentUnavailable { - try await ClerkNativeBridge.shared.signInWithBiometrics( - id: nil, - identifierHint: nil, - reason: nil + XCTFail("Expected biometric reverification to reject before configuration.") + } catch { + let descriptor = ClerkNativeBridge.biometricCredentialErrorDescriptor( + error, + fallbackCode: "unexpected_error" ) + XCTAssertEqual(descriptor.code, "environment_unavailable") } } @@ -164,22 +139,4 @@ final class ClerkNativeBridgeTests: XCTestCase { XCTAssertEqual(result.code, "biometric_credential_policy_incompatible") XCTAssertEqual(result.message, error.localizedDescription) } - - @MainActor - private func assertEnvironmentUnavailable( - _ operation: @MainActor () async throws -> Any, - file: StaticString = #filePath, - line: UInt = #line - ) async { - do { - _ = try await operation() - XCTFail("Expected biometric-credential operation to reject before configuration.", file: file, line: line) - } catch { - let descriptor = ClerkNativeBridge.biometricCredentialErrorDescriptor( - error, - fallbackCode: "unexpected_error" - ) - XCTAssertEqual(descriptor.code, "environment_unavailable", file: file, line: line) - } - } }