TypeScript • npm 1.20.0+ • Expo Go for auth+analytics, Dev Client for push
React Native SDK
First-party RN / Expo SDK. Analytics, identity, push, full auth surface, deep links (Branch parity). Zero-config install — CSPRNG polyfill auto-applied at SDK load.
What's included
- Deep links: typed `linkData<T>` generic, `LinkError` w/ 12 codes, `prewarm` cache, `attachLinkingApi` (one-call replacement for Linking boilerplate), `computeDeviceFingerprint`, `revoke`, `getStats`.
- Auth: anonymous, email+pass, magic, OTP, TOTP MFA, recovery codes, OIDC SSO + deep-link return, Sign in with Apple / Google / Microsoft / LinkedIn / Facebook / Discord. `auth.deleteAccount()` for App Store 5.1.1(v) self-service deletion.
- Encrypted-at-rest tokens (1.18.0, opt-in): pass `init({ secureStorage })` a Keychain / Keystore-backed adapter (`{ getItem, setItem, removeItem }`) and the refresh / access token + cached user route through it. You supply the adapter (Supabase `auth.storage` model) so the SDK never imports `expo-secure-store` — bare-RN stays Metro-safe. Transparent plaintext→adapter migration (no sign-out); graceful fallback. Default = plain AsyncStorage. Full recipe in the `/docs/backend-identity` guide.
- Offline-safe anonymous id (1.17.0): `getAnonymousIdSync(): string | null` — synchronous, non-throwing read for cold-start / offline-first callers (returns null until `init()` hydrate resolves, then the stable id).
- Synchronous verified principal (1.19.0): `getUserSync(): AuthUser | null` returns the hydrated auth user (`{ id, isAnonymous, ... }`) with zero I/O — `id` is the JWT `sub`, the durable key for your backend (not the rotating anonymous id). Pair with `isReady(): boolean` to tell 'still restoring' (`null` + `!isReady()`) apart from 'signed out' (`null` + `isReady()`). Safe on the first call at cold start after `await init()`.
- Analytics: identify / track / screen + auto-tracked lifecycle (`app.opened` / `app.foregrounded` / `app.backgrounded` / `session.start`).
- Push: registers APNs / FCM tokens via `expo-notifications` or `@react-native-firebase/messaging` — **requires Dev Client (Expo Go dropped remote push in SDK 53)**.
- Hermes CSPRNG polyfill bundled + auto-applied — no manual `import "react-native-get-random-values"` in your entry file required (since 0.17.0).
- Runs in Expo Go for auth + analytics + warm deep links. Move to a Dev Client (`npx expo prebuild` or EAS Build) for push registration and Play Install Referrer.
Install
npx expo install @sendoracloud/sdk-react-native @react-native-async-storage/async-storage
# That's it. The CSPRNG polyfill (react-native-get-random-values) is bundled
# as a hard dependency since 0.17.0 and auto-applied at SDK load — no manual
# import "react-native-get-random-values" required in your entry file.Peers
@react-native-async-storage/async-storageRequired peer — persists anonymous id across app restarts.expo-localizationOptional — improves device-fingerprint locale accuracy.expo-cryptoOptional — hardens the SHA-256 used by `computeDeviceFingerprint()`. Falls back to web crypto or pure-JS if missing.react-native-play-install-referrerOptional (Android) — enables 100%-accurate deferred deep-link matching.
Quickstart
import SendoraCloud, { LinkError } from "@sendoracloud/sdk-react-native";
await SendoraCloud.init({
apiKey: "pk_prod_...",
iosBundleId: "com.yourapp",
androidPackageName: "com.yourapp",
});
SendoraCloud.identify("user_123", { email: "u@e.co" });
SendoraCloud.track("signup.completed", { plan: "growth" });
// Deep links — one-call wiring
useEffect(() => {
const off = SendoraCloud.links.onLinkOpened((event) => {
// navigate via event.linkData
});
let detach: (() => void) | undefined;
void SendoraCloud.links
.attachLinkingApi({ onLegacyUrl: (url) => myRouter.handle(url) })
.then((d) => { detach = d; });
void SendoraCloud.links.matchDeferred();
return () => { detach?.(); off(); };
}, []);Security posture
- Public key only — SDK throws on `sk_*` at init.
- HTTPS enforced outside `localhost` / `127.0.0.1`.
- AsyncStorage persists anonymous id only — no PII written to disk.
- Opaque userIds — `identify(userId)` persists to AsyncStorage in plaintext, so pass internal UUIDs, not emails.
- Push token + metadata capped at 4 KB each.
- Bundle-id gate — `links.create()` auto-forwards `iosBundleId` / `androidPackageName`; backend rejects leaked key + wrong bundle.