@akin-travel/sdk-react-native
v0.1.0
Published
React Native binding for the Akin partner SDK — auth (passkey/magic-link) + loyalty, wrapping @akin-travel/sdk-core with native AsyncStorage, @react-native-firebase/auth, and react-native-passkey adapters
Maintainers
Readme
@akin-travel/sdk-react-native
React Native binding for the Akin partner SDK — passkey/magic-link auth and loyalty (tiers, points, history) for partner mobile apps on iOS and Android from one codebase.
It wraps the framework-agnostic @akin-travel/sdk-core with React
Native adapters:
| Concern | sdk-core interface | React Native implementation |
|---|---|---|
| Persistence | StorageAdapter | @react-native-async-storage/async-storage |
| Identity provider | FirebaseAuthClient | @react-native-firebase/auth |
| Passkey ceremony | Authenticator | react-native-passkey (iOS ASAuthorization · Android Credential Manager) |
It exposes the same provider + hook API as the web binding
(@akin-travel/partner-sdk) — AkinProvider, useAkinAuth,
useAkinLoyalty, useSDK — so code is portable between platforms. Parity is
enforced at compile time (src/conformance.ts).
Status: pre-1.0 (
0.1.0). Part of epic akin-travel/issues#2042. The cross-platform conformance/parity suite and example-app QA matrix are owned by #2049.
Install
npm install @akin-travel/sdk-react-native
# peer dependencies (install the ones your app doesn't already have)
npm install @react-native-firebase/app @react-native-firebase/auth \
@react-native-async-storage/async-storage \
react-native-passkeyreact-native-passkey is an optional peer — magic-link and Google sign-in
work without it; it is only required for the native passkey ceremony.
This is a New-Architecture-compatible library with native dependencies, so it
needs a custom dev client / bare workflow (it does not run in Expo Go). With
Expo, use a development build (expo prebuild + expo run:ios|android).
Native setup
Firebase (GIP)
@react-native-firebase/auth auto-initialises from the native config files —
drop them in and no JS Firebase config is required:
- iOS: add
GoogleService-Info.plistto the Xcode project. - Android: add
google-services.jsontoandroid/app/.
The SDK sets the GIP tenant at runtime from config.gipTenantId.
Passkeys — iOS (ASAuthorization)
- Enable the Associated Domains capability in Xcode and add
webcredentials:<your-rp-id>(e.g.webcredentials:akintravel.com). - Host the Apple App Site Association file at
https://<rp-id>/.well-known/apple-app-site-associationwith your app'swebcredentialsentry. Requires iOS 16+.
Passkeys — Android (Credential Manager)
- Host a Digital Asset Links file at
https://<rp-id>/.well-known/assetlinks.jsonincluding your app's signing SHA-256 fingerprint. - Credential Manager passkeys require Android 9+ (API 28+), with the best experience on Android 14+.
The relying-party ID and challenge come from the Akin API (
passkeyAuthenticationOptions); the SDK passes them straight to the native ceremony. You do not configure WebAuthn options in app code.
Quickstart
Wrap your app once:
import { AkinProvider } from '@akin-travel/sdk-react-native';
export default function App() {
return (
<AkinProvider
config={{
partnerId: 'YOUR_PARTNER_ID',
apiKey: 'YOUR_PARTNER_API_KEY',
environment: 'staging', // 'production' | 'staging' | 'development'
gipTenantId: 'YOUR_GIP_TENANT_ID',
verifyCallbackUrl: 'https://yourapp.com/auth/verify',
}}
>
<RootNavigator />
</AkinProvider>
);
}Use the hooks anywhere beneath it:
import { View, Text, Button } from 'react-native';
import { useAkinAuth, useAkinLoyalty } from '@akin-travel/sdk-react-native';
function MemberScreen() {
const { member, isAuthenticated, signInWithPasskey, requestMagicLink, signOut } =
useAkinAuth();
const { tier, points, tierProgress } = useAkinLoyalty();
if (!isAuthenticated) {
return (
<View>
<Button title="Sign in with passkey" onPress={signInWithPasskey} />
<Button title="Email me a magic link" onPress={() => requestMagicLink('[email protected]')} />
</View>
);
}
return (
<View>
<Text>Welcome, {member?.firstName}</Text>
<Text>{points} points · Tier {tier}</Text>
{tierProgress && <Text>{Math.round(tierProgress.progressPercentage)}% to next tier</Text>}
<Button title="Sign out" onPress={signOut} />
</View>
);
}Magic-link verification
The magic-link email opens your verifyCallbackUrl. Capture the token from the
deep link and complete sign-in:
const { verifyMagicLink } = useAkinAuth();
const { success, isNewUser } = await verifyMagicLink(tokenFromDeepLink);API
useAkinAuth()
State: user, member, isAuthenticated, isLoading, error.
Actions: signIn, signUp, signUpPasswordless, requestMagicLink,
verifyMagicLink, signInWithPasskey, signOut, resetPassword,
refreshMemberData, clearError.
useAkinLoyalty()
State: points, akinPoints, tier, tierConfigs, tierConfig,
tierProgress, transactions, tierHistory, upcomingStays, previousStays,
membershipYearStats, lifetimeStats, tierBenefits, isLoading, error.
Actions: refreshData, getTransactionHistory, getTierHistory.
useSDK()
config, plus the live adapters (storage, authenticator,
firebaseAuthClient) for advanced use.
Custom adapters
The provider builds the default native adapters automatically. To inject your
own (e.g. a Keychain/Keystore-backed StorageAdapter, or for testing):
import {
AkinProvider,
createAsyncStorageAdapter,
createReactNativeFirebaseAuthClient,
} from '@akin-travel/sdk-react-native';
<AkinProvider config={config} adapters={{ storage: myKeychainAdapter }}>
{children}
</AkinProvider>Localise auth error copy with the errorMessages prop (keyed by the stable
AkinAuthErrorCode from sdk-core).
Example app
A runnable Expo example lives in example/ — a single screen that
signs in (passkey or magic-link) and renders the member's tier and points.
Development
npm run build # tsc → dist (also runs the compile-time parity conformance)
npm test # vitest — adapters, passkey flow, tier-progress, member mapper, exportsThe RN peer modules are not installed in the akin-online monorepo; the adapters
are dependency-injected and unit-tested with fakes, and the native imports are
isolated to src/native/defaults.ts (typed via src/native-modules.d.ts).
