Skip to content

Commit aaa302d

Browse files
committed
feat(android): add support for Firebase Phone Number Verification (PNV)
Bridge all fields from the native Firebase PNV SDK that were previously omitted. VerificationSupportResult now exposes simSlot, carrierId, and reason (mapped to VerificationSupportStatus strings). VerifiedPhoneNumberTokenResult now exposes expirationTimestamp, issuedAtTimestamp, nonce, and claims. Error handling extracts structured error codes from FirebasePhoneNumberVerificationException (9 SDK codes). Added getVerificationSupportInfo(simSlot) overload for querying specific SIM slots. Updated TypeScript types, e2e tests, local test app, and docs with error handling table, fallback examples, test-mode setup, and region/carrier limitations.
1 parent 454364c commit aaa302d

6 files changed

Lines changed: 374 additions & 40 deletions

File tree

docs/phone-number-verification/usage/index.mdx

Lines changed: 133 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,89 @@ Key capabilities:
3131

3232
- **Carrier-level verification**: Verifies phone numbers directly with the mobile carrier, without SMS.
3333
- **Support detection**: Check whether the device and carrier support phone number verification before attempting it. This does not require user consent.
34-
- **Verified phone number**: Retrieve the device's verified phone number. This will present a consent dialog to the user.
34+
- **Verified phone number**: Retrieve the device's verified phone number as a JWT token containing the phone number, timestamps, nonce, and claims. This will present a consent dialog to the user.
3535
- **Digital Credential API**: Supports the Android Digital Credential API for custom verification flows.
3636

37+
## Region & carrier limitations
38+
39+
PNV depends on carrier cooperation. Not all carriers or regions are supported. Before relying on PNV, always call `getVerificationSupportInfo()` to check support. If a SIM slot returns `reason: 'INCAPABLE_DUE_TO_CARRIER_UNSUPPORTED'`, that carrier does not participate in PNV and you should fall back to another verification method (e.g. Firebase Auth SMS).
40+
41+
Common reasons verification may be unsupported:
42+
43+
| Reason | Meaning |
44+
|---|---|
45+
| `CAPABLE` | The SIM's carrier supports PNV. |
46+
| `INCAPABLE_DUE_TO_CARRIER_UNSUPPORTED` | The carrier does not participate in PNV. |
47+
| `INCAPABLE_DUE_TO_ANDROID_VERSION` | The device's Android version is too old. |
48+
| `INCAPABLE_DUE_TO_SIM_STATE` | No SIM inserted, or SIM is in an unusable state. |
49+
| `CAPABILITY_STATUS_UNSPECIFIED` | The SDK could not determine the status. |
50+
3751
# Usage
3852

3953
## Check verification support
4054

41-
Before attempting verification, check if the device's SIM card and carrier support phone number verification. This call does not require user consent and can be called freely:
55+
Before attempting verification, check if the device's SIM card(s) support phone number verification. This call does not require user consent and can be called freely:
4256

4357
```js
4458
import { getVerificationSupportInfo } from '@react-native-firebase/phone-number-verification';
4559

4660
const supportInfo = await getVerificationSupportInfo();
4761

4862
for (const info of supportInfo) {
49-
console.log('SIM supported:', info.isSupported);
63+
console.log(`SIM slot ${info.simSlot}:`);
64+
console.log(' Supported:', info.isSupported);
65+
console.log(' Carrier ID:', info.carrierId);
66+
console.log(' Reason:', info.reason);
5067
}
5168
```
5269

53-
The method returns an array with one entry per SIM slot. Each entry indicates whether that SIM's carrier supports PNV.
70+
The method returns an array with one entry per SIM slot. Each entry includes:
71+
72+
- `isSupported` — whether PNV is available for this SIM.
73+
- `simSlot` — the SIM slot index (0-based).
74+
- `carrierId` — the carrier identifier string.
75+
- `reason` — a `VerificationSupportStatus` string explaining why the SIM is or isn't supported.
76+
77+
### Query a specific SIM slot
78+
79+
On dual-SIM devices, you can query a specific SIM slot by passing the slot index:
80+
81+
```js
82+
import { getVerificationSupportInfo } from '@react-native-firebase/phone-number-verification';
83+
84+
const supportInfo = await getVerificationSupportInfo(0); // SIM slot 0
85+
```
86+
87+
### Fallback when unsupported
88+
89+
If PNV is not supported, fall back to an alternative verification method:
90+
91+
```js
92+
import { Platform } from 'react-native';
93+
import { getVerificationSupportInfo, getVerifiedPhoneNumber } from '@react-native-firebase/phone-number-verification';
94+
95+
async function verifyPhoneNumber() {
96+
if (Platform.OS !== 'android') {
97+
// Use SMS-based verification on non-Android platforms
98+
return verifySms();
99+
}
100+
101+
const supportInfo = await getVerificationSupportInfo();
102+
const supported = supportInfo.some(info => info.isSupported);
103+
104+
if (supported) {
105+
try {
106+
return await getVerifiedPhoneNumber();
107+
} catch (error) {
108+
// Fall back to SMS on failure
109+
return verifySms();
110+
}
111+
}
112+
113+
// Carrier or device doesn't support PNV
114+
return verifySms();
115+
}
116+
```
54117

55118
## Verify a phone number
56119

@@ -64,13 +127,24 @@ import { getVerifiedPhoneNumber } from '@react-native-firebase/phone-number-veri
64127
try {
65128
const result = await getVerifiedPhoneNumber();
66129
console.log('Phone number:', result.phoneNumber);
67-
console.log('Verification token:', result.token);
130+
console.log('Token:', result.token);
131+
console.log('Expires at:', new Date(result.expirationTimestamp * 1000));
132+
console.log('Issued at:', new Date(result.issuedAtTimestamp * 1000));
133+
console.log('Nonce:', result.nonce);
134+
console.log('Claims:', result.claims);
68135
} catch (error) {
69-
console.error('Verification failed:', error);
136+
console.error('Verification failed:', error.code, error.message);
70137
}
71138
```
72139

73-
The returned `token` can be sent to your backend server to validate the verification with Firebase.
140+
The returned result includes:
141+
142+
- `phoneNumber` — the verified phone number in E.164 format.
143+
- `token` — the raw JWT token string for server-side validation.
144+
- `expirationTimestamp` — token expiration as Unix epoch seconds.
145+
- `issuedAtTimestamp` — token issued-at time as Unix epoch seconds.
146+
- `nonce` — the nonce from the JWT payload, or `null`.
147+
- `claims` — all JWT claims as a key-value map, or `null`.
74148

75149
## Custom verification with Digital Credentials
76150

@@ -91,20 +165,67 @@ const payload = await getDigitalCredentialPayload('your-unique-nonce');
91165
// Step 3: Exchange the response for a verified phone number
92166
const result = await exchangeCredentialResponseForPhoneNumber(credentialResponse);
93167
console.log('Phone number:', result.phoneNumber);
168+
console.log('Expires at:', new Date(result.expirationTimestamp * 1000));
169+
```
170+
171+
## Error handling
172+
173+
All methods reject with structured error codes from the Firebase PNV SDK. The `error.code` property contains one of these values:
174+
175+
| Error Code | Meaning |
176+
|---|---|
177+
| `carrier-not-supported` | The SIM's carrier does not support PNV. |
178+
| `invalid-digital-credential-response` | The Digital Credential API response was invalid. |
179+
| `integrity-check-failed` | Device integrity check failed. |
180+
| `preflight-check-failed` | Server-side preflight check failed. |
181+
| `unsupported-operation` | The API call is not supported with the given parameters. |
182+
| `credential-manager-error` | Android Credential Manager failed unexpectedly. |
183+
| `invalid-test-number-id` | Test number IDs are empty, expired, or duplicated. |
184+
| `test-session-already-enabled` | `enableTestSession` was called more than once. |
185+
| `activity-context-required` | An Activity context is required (app may be in the background). |
186+
187+
```js
188+
import { getVerifiedPhoneNumber } from '@react-native-firebase/phone-number-verification';
189+
190+
try {
191+
const result = await getVerifiedPhoneNumber();
192+
} catch (error) {
193+
switch (error.code) {
194+
case 'carrier-not-supported':
195+
// Fall back to SMS verification
196+
break;
197+
case 'activity-context-required':
198+
// Retry when app is in foreground
199+
break;
200+
default:
201+
console.error('PNV error:', error.code, error.message);
202+
}
203+
}
94204
```
95205

96206
## Testing
97207

98-
For testing without a real SIM card, you can enable a test session using a token from the Firebase Console:
208+
To test without a real SIM card and carrier, use Firebase's test mode. This requires setup in the Firebase Console:
209+
210+
1. **Generate a test token**: In the Firebase Console, navigate to Phone Number Verification and generate a test token. Test tokens have a 7-day TTL.
211+
2. **IAM permissions**: Ensure the service account has the required `firebasepnv.testSessions.create` permission.
212+
3. **Google system services beta**: On the test device, enroll the Google system services app into the beta channel via Google Play.
213+
4. **Call `enableTestSession` once**: Pass the token before any verification calls. This must be called only once per app instance — calling it again will reject with `test-session-already-enabled`.
99214

100215
```js
101-
import { enableTestSession } from '@react-native-firebase/phone-number-verification';
216+
import {
217+
enableTestSession,
218+
getVerifiedPhoneNumber,
219+
} from '@react-native-firebase/phone-number-verification';
102220

103-
// Enable test mode - call only once
221+
// Call once at app startup for testing
104222
await enableTestSession('your-test-token-from-firebase-console');
105-
```
106223

107-
In test mode, phone numbers follow the format: a valid country code followed by all zeros.
224+
// Now verification calls return test data
225+
// Phone numbers in test mode follow the format: valid country code + all zeros
226+
const result = await getVerifiedPhoneNumber();
227+
console.log('Test phone number:', result.phoneNumber);
228+
```
108229

109230
## Platform handling
110231

0 commit comments

Comments
 (0)