Skip to content

Report a started biometric prompt as started - #522

Open
prosenthal828 wants to merge 1 commit into
NativePHP:mainfrom
prosenthal828:fix/biometric-prompt-result
Open

prosenthal828 wants to merge 1 commit into
NativePHP:mainfrom
prosenthal828:fix/biometric-prompt-result

Conversation

@prosenthal828

Copy link
Copy Markdown

Problem

PendingBiometric::prompt() returns true only when the bridge answers {"status": "success"}. No bridge function answers that: BridgeResponse.success() (Kotlin and Swift) returns the function's data as-is, and only errors carry a status key ({"status": "error", "code": ..., "message": ...}).

nativephp/mobile-biometrics 1.0.3 answers {"launched": true} on Android and {} on iOS, so Biometrics::prompt()->prompt() returns false while the fingerprint / Touch ID sheet is on screen. Code that checks the return value (for example to fall back to a PIN when biometrics can't start) takes the fallback every time.

Fix

Treat any answer that isn't {"status": "error", ...} as started. PendingMicrophone::start() already does this. A missing bridge function (nativephp_call() returns null) still returns false. The result of the prompt still arrives as Native\Mobile\Events\Biometric\Completed.

The plugin is not the side to change: {"launched": true} / {} follow the core bridge contract, and wrapping its answer in status: success would make it the only bridge function that does.

Tests

New tests/Unit/Biometrics/PendingBiometricTest.php (FakeBridge):

  • sends id and the event class to Biometric.Prompt
  • {"launched":true}, {} and [] count as started (these fail on main)
  • {"status":"error", ...} and a missing bridge function don't
  • prompt() only calls the bridge once

Full suite on the branch: 1499 passed, 3 skipped, 4 risky (same skipped/risky as main).

Tested on real devices

Core 4.5.2 with this change, nativephp/mobile-biometrics 1.0.3, debug builds. A test app routed its unlock screen through Biometrics::prompt()->id($id)->prompt() and logged the return value.

  • Galaxy A54 (SM-A546U1): the bridge answered {"launched":true} and prompt() returned true. The system biometric prompt was on screen (BiometricPromptRoot window). A real fingerprint then delivered Completed with success: true and the matching id. Seen twice.
  • iPad Air M3: prompt() returned true and Touch ID came up. A real Touch ID then delivered Completed with success: true. Seen twice.

Same pattern, not changed here: PendingPhotoCapture, PendingVideoRecorder and PendingMediaPicker also compare against status === 'success'. Happy to send those as a follow-up if you want them fixed the same way.

🤖 Generated with Claude Code

PendingBiometric::prompt() only returned true for {"status": "success"},
but BridgeResponse.success() on Android and iOS returns the function's data
unwrapped and only errors carry a status key. mobile-biometrics answers
{"launched": true} on Android and {} on iOS, so prompt() returned false for
a prompt that was on screen.

Treat any answer that is not {"status": "error", ...} as started, the same
check PendingMicrophone already uses. A missing bridge function (null) still
returns false.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant