Skip to content

fix: propagate native error message instead of discarding it#109

Open
guidofawkes328 wants to merge 1 commit into
f-23:stablefrom
guidofawkes328:fix/propagate-native-error-message
Open

fix: propagate native error message instead of discarding it#109
guidofawkes328 wants to merge 1 commit into
f-23:stablefrom
guidofawkes328:fix/propagate-native-error-message

Conversation

@guidofawkes328

Copy link
Copy Markdown

Problem

On Android, create() / get() reject with the coarse mapped error code as both the code and the message:

promise.reject(errorCode, errorCode)

This discards the real CreateCredentialException.errorMessage (e.g. "RP ID cannot be validated."). The JS wrapper (handleNativeError) then maps the code to a hardcoded generic string, so callers only ever see something like "The request failed. No Credentials were returned." — misleading, and useless for diagnosing configuration problems (RP ID / assetlinks mismatches, etc.).

Several distinct DOM errors (NotAllowedError, DataError = "RP ID cannot be validated", SecurityError) all collapse to RequestFailed, so the code alone cannot tell them apart — the message is the only signal, and it was being thrown away.

There was also no fallback catch around the coroutine body, so a non-CredentialException failure could leave the promise unsettled (a hang).

Fix

Non-breaking — error codes are unchanged, so programmatic .error === 'RequestFailed' checks keep working. Only the message now carries the native detail.

  • Android (PasskeyModule.kt): reject with the native message while keeping the code:
    promise.reject(errorCode, e.errorMessage?.toString() ?: errorCode)
    for both create() and get(), plus a catch (e: Throwable) fallback so unexpected failures reject with detail instead of hanging.
  • iOS (Passkey.swift): the primary error path already propagated error.localizedDescription. Aligned the get() decode-catch with create() so it rejects with a mapped code + localizedDescription instead of the verbose debugDescription for both fields.
  • JS (PasskeyError.ts): prefer the native message over the generic per-code fallback when it carries additional detail; fall back to the existing generic message otherwise. Added message?: string to TNativeError.

Before / After

Given a native failure with errorMessage = "RP ID cannot be validated.":

  • Before: { error: 'RequestFailed', message: 'The request failed. No Credentials were returned.' }
  • After: { error: 'RequestFailed', message: 'RP ID cannot be validated.' }

Verification

yarn tsc --noEmit, eslint, and the Jest suite all pass. Kotlin/Swift were not compiled in isolation but the edits are syntactically consistent with the surrounding code and existing imports.

Related

Android rejected create()/get() with the mapped error code as both the
code and the message (`promise.reject(errorCode, errorCode)`), discarding
the underlying `CreateCredentialException.errorMessage` (e.g. "RP ID
cannot be validated."). JS callers therefore only saw a generic, often
misleading message and could not diagnose configuration problems.

- Android: reject with the native `errorMessage` as the message while
  keeping the existing code, and add a `catch (Throwable)` fallback so a
  non-CredentialException failure rejects with detail instead of leaving
  the promise unsettled (a hang).
- iOS: align the get() decode-catch with create() so it rejects with a
  mapped code + `localizedDescription` instead of the verbose
  debugDescription for both fields.
- JS: prefer the native message over the generic per-code fallback when
  it carries additional detail, keeping the mapped error code unchanged.

Error codes are unchanged, so programmatic `.error` checks keep working.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error: [50152] RP ID cannot be validated on Android

1 participant