fix: normalize cancellation / no-credential errors on Android & iOS (#106, #107)#108
Open
plgrazon wants to merge 1 commit into
Open
fix: normalize cancellation / no-credential errors on Android & iOS (#106, #107)#108plgrazon wants to merge 1 commit into
plgrazon wants to merge 1 commit into
Conversation
Both behaviors regressed in e8eb940 (3.5.0, getImmediate feature). Android (f-23#106): a Back-button cancel during get() surfaces as GetPublicKeyCredentialDomException(NotAllowedError), which mapDomError collapsed to RequestFailed. WebAuthn uses NotAllowedError as the (deliberately ambiguous) user-abort signal, so map it to UserCancelled — consistent with iOS .canceled and the web navigator.credentials convention. Genuine config failures still surface as SecurityError / ConstraintError / DataError and keep their distinct codes. iOS (f-23#107): immediate mediation reports "no credential available" as ASAuthorizationError.canceled (1001), not notInteractive (1005) as the original mapping assumed, so getImmediate() returned UserCancelled when no passkey existed. Thread the immediate flag into handleErrorCode and map a 1001 to NoCredentials when the request was immediate (iOS 16+); a normal get() cancel still maps to UserCancelled. Also removes a dead duplicate case 1006. Adds JS rejection-path tests (handleNativeError unit test plus get / getImmediate reject cases) which the repo previously lacked.
plgrazon
force-pushed
the
fix/cancel-nocredential-errors
branch
from
July 6, 2026 17:52
c0bcfec to
ab9094f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two error-mapping regressions introduced in
e8eb940(released as 3.5.0, thegetImmediatefeature).#106 — Android: Back button during
get()returnsRequestFailedinstead ofUserCancellede8eb940addedmapDomError, which mappedNotAllowedError -> "RequestFailed". On Android, the Credential Manager / FIDO2 provider surfaces a Back-button ceremony cancel asGetPublicKeyCredentialDomException(NotAllowedError)(WebAuthn deliberately usesNotAllowedErroras the ambiguous user-abort/timeout signal) — not asGetCredentialCancellationException. So the cancel was deterministically collapsed toRequestFailed("The request failed. No Credentials were returned.").Fix: map
NotAllowedError -> "UserCancelled", consistent with iOS.canceledand the webnavigator.credentialsconvention. Genuine misconfiguration still surfaces asSecurityError/ConstraintError/DataErrorand keeps its distinct code.#107 — iOS:
getImmediate()returnsUserCancelledinstead ofNoCredentialswhen no passkey existse8eb940addedcase 1005 -> .noCredentials, assuming an immediate-mediation "no credentials" outcome returnsASAuthorizationError.notInteractive(1005). It doesn't —performRequests(options: .preferImmediatelyAvailableCredentials)with no on-device credential fails withASAuthorizationError.canceled(1001), the same code as a genuine user cancel. The delegate forwarded the raw error with no knowledge the request was immediate.Fix: thread the
preferImmediatelyAvailableflag intohandleErrorCodeand map a1001toNoCredentialswhen the request was immediate (gated on iOS 16+, where the option actually engages). A normalget()cancel still maps toUserCancelled. Also removes a dead duplicatecase 1006.After both fixes,
getImmediatewith no passkey returnsNoCredentialson both platforms (Android already did viaNoCredentialException), matching the documented contract.Design notes
1001is also reported asNoCredentials. Acceptable for a silent-probe API — consumers fall back either way.Tests
The specs previously covered only success/arg-plumbing (no rejection path — which is how both regressions shipped). Added:
src/__tests__/PasskeyError.spec.ts— unit tests forhandleNativeErrorcode mapping.get()->UserCancelled,getImmediate()->NoCredentials).yarn test(24 passing),yarn typescript, andyarn lintall green.Verification
JS tests cannot exercise the Kotlin/Swift changes. Native behavior verified on real devices:
get()+ hardware Back on the Credential Manager prompt ->UserCancelled.getImmediate()with no passkey ->NoCredentials; normalget()cancel still ->UserCancelled.Closes #106
Closes #107
🤖 Generated with Claude Code