# Introduction (/docs/introduction) Welcome to Teardown. We build better tools for React Native Engineers to build better products and ship products faster. ## Quick Links * [React Native SDK](/docs/react-native) - Force update your app and track users and devices across your mobile apps. # BasicAdapter (/docs/react-native/adapters/device/basic) The `BasicAdapter` provides minimal device information without external dependencies. Useful for testing or fallback scenarios. ## Usage ```typescript import { TeardownCore } from '@teardown/react-native'; import { BasicAdapter } from '@teardown/react-native/adapters/basic'; import { MMKVStorageAdapter } from '@teardown/react-native/adapters/mmkv'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new BasicAdapter(), }); ``` ## Import Path ```typescript import { BasicAdapter } from '@teardown/react-native/adapters/basic'; ``` ## Collected Data The BasicAdapter returns static or minimal information: ### Application Info * `version` - Static value or from app.json * `buildNumber` - Static value * `bundleId` - Static value or from app.json ### Hardware Info * `deviceName` - "Unknown Device" * `brand` - "Unknown" * `deviceType` - "unknown" ### OS Info * `osName` - From React Native Platform API * `osVersion` - From React Native Platform API ## When to Use * Unit testing without native modules * Development/prototyping * Fallback when other adapters fail * Environments where native modules aren't available ## Limitations * Limited device information accuracy * No hardware-specific details * Not recommended for production analytics # DeviceInfoAdapter (/docs/react-native/adapters/device/device-info) The `DeviceInfoAdapter` uses `react-native-device-info` to collect device information in bare React Native projects. ## Installation ```bash npm install react-native-device-info # or bun add react-native-device-info ``` For iOS, run pod install: ```bash cd ios && pod install ``` ## Usage ```typescript import { TeardownCore } from '@teardown/react-native'; import { DeviceInfoAdapter } from '@teardown/react-native/adapters/device-info'; import { MMKVStorageAdapter } from '@teardown/react-native/adapters/mmkv'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new DeviceInfoAdapter(), }); ``` ## Import Path ```typescript import { DeviceInfoAdapter } from '@teardown/react-native/adapters/device-info'; ``` ## Collected Data ### Application Info * `version` - From `getVersion()` * `buildNumber` - From `getBuildNumber()` * `bundleId` - From `getBundleId()` ### Hardware Info * `deviceName` - From `getDeviceName()` * `brand` - From `getBrand()` * `deviceType` - From `getDeviceType()` (phone, tablet, desktop, tv, unknown) ### OS Info * `osName` - From `getSystemName()` * `osVersion` - From `getSystemVersion()` ## When to Use * Bare React Native CLI projects * Projects not using Expo modules * Projects requiring additional device info beyond what Expo provides ## Requirements * `react-native-device-info` >= 10.0.0 * React Native >= 0.60 (autolinking) ## Platform Notes ### iOS Requires `pod install` after installation. ### Android Works out of the box with autolinking. # ExpoDeviceAdapter (/docs/react-native/adapters/device/expo) The `ExpoDeviceAdapter` uses Expo's native modules to collect device information. ## Installation ```bash npx expo install expo-device expo-application ``` ## Usage ```typescript import { TeardownCore } from '@teardown/react-native'; import { ExpoDeviceAdapter } from '@teardown/react-native/adapters/expo'; import { MMKVStorageAdapter } from '@teardown/react-native/adapters/mmkv'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); ``` ## Import Path ```typescript import { ExpoDeviceAdapter } from '@teardown/react-native/adapters/expo'; ``` ## Collected Data ### Application Info * `version` - From `expo-application` `nativeApplicationVersion` * `buildNumber` - From `expo-application` `nativeBuildVersion` * `bundleId` - From `expo-application` `applicationId` ### Hardware Info * `deviceName` - From `expo-device` `deviceName` * `brand` - From `expo-device` `brand` * `deviceType` - From `expo-device` `deviceType` (phone, tablet, desktop, tv, unknown) ### OS Info * `osName` - From `expo-device` `osName` * `osVersion` - From `expo-device` `osVersion` ## When to Use * Expo managed workflow projects * Expo bare workflow projects using Expo modules * Projects using Expo SDK 44+ ## Requirements * `expo-device` >= 4.0.0 * `expo-application` >= 4.0.0 # Device Adapters (/docs/react-native/adapters/device) Device adapters collect device and application information for identification and analytics. You must provide a device adapter when initializing `TeardownCore`. ## Available Adapters | Adapter | Package | Best For | | --------------------------------------------------------------------- | --------------------------------- | ---------------- | | [`ExpoDeviceAdapter`](/docs/react-native/adapters/device/expo) | `expo-device`, `expo-application` | Expo projects | | [`DeviceInfoAdapter`](/docs/react-native/adapters/device/device-info) | `react-native-device-info` | Bare RN projects | | [`BasicAdapter`](/docs/react-native/adapters/device/basic) | None | Fallback/testing | ## Collected Information All device adapters provide: ### Application Info ```typescript interface ApplicationInfo { version: string; // App version (e.g., "1.2.3") buildNumber: string; // Build number (e.g., "45") bundleId: string; // Bundle identifier (e.g., "com.example.app") } ``` ### Hardware Info ```typescript interface HardwareInfo { deviceName: string; // Device name (e.g., "iPhone 14 Pro") brand: string; // Manufacturer (e.g., "Apple") deviceType: string; // Device type (e.g., "phone", "tablet") } ``` ### OS Info ```typescript interface OSInfo { osName: string; // OS name (e.g., "iOS", "Android") osVersion: string; // OS version (e.g., "17.0") } ``` ## Usage ```typescript import { TeardownCore } from '@teardown/react-native'; import { ExpoDeviceAdapter } from '@teardown/react-native/adapters/expo'; const teardown = new TeardownCore({ deviceAdapter: new ExpoDeviceAdapter(), // ... other options }); ``` ## Device ID The SDK generates a stable device ID that persists across app sessions: ```typescript const deviceId = await core.device.getDeviceId(); // Returns a UUID that persists in storage ``` ## Custom Adapter Implement the `DeviceInfoAdapter` interface: ```typescript import type { DeviceInfoAdapter, ApplicationInfo, HardwareInfo, OSInfo } from '@teardown/react-native'; class CustomDeviceAdapter implements DeviceInfoAdapter { get applicationInfo(): ApplicationInfo { return { version: '1.0.0', buildNumber: '1', bundleId: 'com.example.app', }; } get hardwareInfo(): HardwareInfo { return { deviceName: 'Custom Device', brand: 'Custom', deviceType: 'phone', }; } get osInfo(): OSInfo { return { osName: 'iOS', osVersion: '17.0', }; } } ``` # Adapters (/docs/react-native/adapters) The Teardown SDK uses adapters to abstract platform-specific functionality. This allows the SDK to work across different React Native environments (Expo, bare RN) and with different underlying libraries. ## Adapter Types ### Device Adapters Collect device and application information for identification and analytics. | Adapter | Package | Use Case | | --------------------------------------------------------------------- | --------------------------------- | ---------------- | | [`ExpoDeviceAdapter`](/docs/react-native/adapters/device/expo) | `expo-device`, `expo-application` | Expo projects | | [`DeviceInfoAdapter`](/docs/react-native/adapters/device/device-info) | `react-native-device-info` | Bare RN projects | | [`BasicAdapter`](/docs/react-native/adapters/device/basic) | None | Fallback/testing | [View Device Adapters →](/docs/react-native/adapters/device) ### Storage Adapters Handle persistent data storage for sessions, device IDs, and version status. | Adapter | Package | Use Case | | -------------------------------------------------------------------------- | ------------------------------------------- | ------------------------------- | | [`MMKVStorageAdapter`](/docs/react-native/adapters/storage/mmkv) | `react-native-mmkv` | Fast sync storage (recommended) | | [`AsyncStorageAdapter`](/docs/react-native/adapters/storage/async-storage) | `@react-native-async-storage/async-storage` | Broader compatibility | [View Storage Adapters →](/docs/react-native/adapters/storage) ### Notification Adapters Handle push notification registration and token management. | Adapter | Package | Use Case | | -------------------------------------------------------------------------------- | ---------------------------------- | ------------------------- | | [`ExpoNotificationsAdapter`](/docs/react-native/adapters/notifications/expo) | `expo-notifications` | Expo projects | | [`FirebaseMessagingAdapter`](/docs/react-native/adapters/notifications/firebase) | `@react-native-firebase/messaging` | Firebase Cloud Messaging | | [`WixNotificationsAdapter`](/docs/react-native/adapters/notifications/wix) | `react-native-notifications` | Wix notifications library | [View Notification Adapters →](/docs/react-native/adapters/notifications) ## Required Adapters When initializing `TeardownCore`, you must provide: ```typescript const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), // Required deviceAdapter: new ExpoDeviceAdapter(), // Required }); ``` ## Common Configurations ### Expo Project ```typescript import { TeardownCore } from '@teardown/react-native'; import { ExpoDeviceAdapter } from '@teardown/react-native/adapters/expo'; import { MMKVStorageAdapter } from '@teardown/react-native/adapters/mmkv'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); ``` ### Bare React Native Project ```typescript import { TeardownCore } from '@teardown/react-native'; import { DeviceInfoAdapter } from '@teardown/react-native/adapters/device-info'; import { AsyncStorageAdapter } from '@teardown/react-native/adapters/async-storage'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new AsyncStorageAdapter(), deviceAdapter: new DeviceInfoAdapter(), }); ``` ## Custom Adapters All adapter types can be extended for custom implementations. See individual adapter sections for interface definitions. # ExpoNotificationsAdapter (/docs/react-native/adapters/notifications/expo) The `ExpoNotificationsAdapter` integrates with Expo's notifications library for push notification handling. ## Installation ```bash npx expo install expo-notifications expo-device expo-constants ``` ## Usage ```typescript import { ExpoNotificationsAdapter } from '@teardown/react-native/expo-notifications'; const notifications = new ExpoNotificationsAdapter(); // Request permissions const granted = await notifications.requestPermissions(); if (granted) { // Get push token const token = await notifications.getToken(); console.log('Push token:', token); } ``` ## Import Path ```typescript import { ExpoNotificationsAdapter } from '@teardown/react-native/expo-notifications'; ``` ## API ### getToken() Get the Expo push token: ```typescript const token = await notifications.getToken(); // Returns: "ExponentPushToken[xxxxxx]" or null ``` ### requestPermissions() Request notification permissions: ```typescript const granted = await notifications.requestPermissions(); // Returns: true if granted, false otherwise ``` ### isEnabled() Check if notifications are enabled: ```typescript const enabled = await notifications.isEnabled(); ``` ### onNotification() Subscribe to incoming notifications while app is foregrounded: ```typescript const unsubscribe = notifications.onNotification((notification) => { console.log('Received:', notification.title); }); // Cleanup unsubscribe(); ``` ### onNotificationResponse() Subscribe to notification responses (user taps): ```typescript const unsubscribe = notifications.onNotificationResponse((response) => { console.log('User tapped notification:', response.notification.title); }); // Cleanup unsubscribe(); ``` ## Configuration ### app.json / app.config.js ```json { "expo": { "plugins": [ [ "expo-notifications", { "icon": "./assets/notification-icon.png", "color": "#ffffff" } ] ] } } ``` ### Android For FCM integration, add your `google-services.json` to the project root. ### iOS Push notifications require: * Apple Developer account * Push notification capability * APNs certificate or key ## Requirements * `expo-notifications` >= 0.17.0 * `expo-device` >= 4.0.0 * `expo-constants` >= 13.0.0 * EAS Build or dev client (not Expo Go) # FirebaseMessagingAdapter (/docs/react-native/adapters/notifications/firebase) The `FirebaseMessagingAdapter` integrates with Firebase Cloud Messaging for push notifications. ## Installation ```bash npm install @react-native-firebase/app @react-native-firebase/messaging # or bun add @react-native-firebase/app @react-native-firebase/messaging ``` For iOS: ```bash cd ios && pod install ``` ## Usage ```typescript import { FirebaseMessagingAdapter } from '@teardown/react-native/firebase-messaging'; const notifications = new FirebaseMessagingAdapter(); // Request permissions (iOS) const granted = await notifications.requestPermissions(); if (granted) { // Get FCM token const token = await notifications.getToken(); console.log('FCM token:', token); } ``` ## Import Path ```typescript import { FirebaseMessagingAdapter } from '@teardown/react-native/firebase-messaging'; ``` ## API ### getToken() Get the FCM registration token: ```typescript const token = await notifications.getToken(); // Returns: FCM token string or null ``` ### requestPermissions() Request notification permissions (required on iOS): ```typescript const granted = await notifications.requestPermissions(); // Returns: true if granted, false otherwise ``` ### isEnabled() Check if notifications are enabled: ```typescript const enabled = await notifications.isEnabled(); ``` ### onNotification() Subscribe to incoming notifications: ```typescript const unsubscribe = notifications.onNotification((notification) => { console.log('Received:', notification.title); }); // Cleanup unsubscribe(); ``` ### onNotificationResponse() Subscribe to notification opens: ```typescript const unsubscribe = notifications.onNotificationResponse((response) => { console.log('User tapped:', response.notification.title); }); // Cleanup unsubscribe(); ``` ## Configuration ### Android 1. Add `google-services.json` to `android/app/` 2. Add to `android/build.gradle`: ```groovy buildscript { dependencies { classpath 'com.google.gms:google-services:4.3.15' } } ``` 3. Add to `android/app/build.gradle`: ```groovy apply plugin: 'com.google.gms.google-services' ``` ### iOS 1. Add `GoogleService-Info.plist` to iOS project 2. Enable Push Notifications capability in Xcode 3. Upload APNs key to Firebase Console ## Background Messages Handle messages when app is in background: ```typescript import messaging from '@react-native-firebase/messaging'; // Register background handler (must be outside component) messaging().setBackgroundMessageHandler(async (remoteMessage) => { console.log('Background message:', remoteMessage); }); ``` ## Requirements * `@react-native-firebase/app` >= 18.0.0 * `@react-native-firebase/messaging` >= 18.0.0 * React Native >= 0.71 # Notification Adapters (/docs/react-native/adapters/notifications) Notification adapters handle push notification token registration and management with your push notification provider. ## Available Adapters | Adapter | Package | Best For | | -------------------------------------------------------------------------------- | ---------------------------------- | ------------------------- | | [`ExpoNotificationsAdapter`](/docs/react-native/adapters/notifications/expo) | `expo-notifications` | Expo projects | | [`FirebaseMessagingAdapter`](/docs/react-native/adapters/notifications/firebase) | `@react-native-firebase/messaging` | Firebase Cloud Messaging | | [`WixNotificationsAdapter`](/docs/react-native/adapters/notifications/wix) | `react-native-notifications` | Wix notifications library | ## Usage Notification adapters are optional and used separately from the core SDK: ```typescript import { ExpoNotificationsAdapter } from '@teardown/react-native/expo-notifications'; const notificationsAdapter = new ExpoNotificationsAdapter(); // Get push token const token = await notificationsAdapter.getToken(); // Register token with your backend await registerPushToken(token); ``` ## Adapter Interface All notification adapters implement: ```typescript interface NotificationsAdapter { /** Get the push notification token */ getToken(): Promise; /** Request notification permissions */ requestPermissions(): Promise; /** Check if notifications are enabled */ isEnabled(): Promise; /** Subscribe to incoming notifications */ onNotification(handler: (notification: Notification) => void): () => void; /** Subscribe to notification responses (user taps) */ onNotificationResponse(handler: (response: NotificationResponse) => void): () => void; } ``` ## Choosing an Adapter | If you're using... | Use this adapter | | ------------------------------ | -------------------------- | | Expo with expo-notifications | `ExpoNotificationsAdapter` | | Firebase Cloud Messaging | `FirebaseMessagingAdapter` | | Wix react-native-notifications | `WixNotificationsAdapter` | | Other push library | Implement custom adapter | ## Custom Adapter Implement the `NotificationsAdapter` interface: ```typescript import type { NotificationsAdapter, Notification, NotificationResponse } from '@teardown/react-native'; class CustomNotificationsAdapter implements NotificationsAdapter { async getToken(): Promise { // Return push token from your notification library return await myNotificationLib.getToken(); } async requestPermissions(): Promise { // Request notification permissions return await myNotificationLib.requestPermission(); } async isEnabled(): Promise { // Check if notifications are enabled return await myNotificationLib.areNotificationsEnabled(); } onNotification(handler: (notification: Notification) => void): () => void { // Subscribe to incoming notifications const subscription = myNotificationLib.onNotification(handler); return () => subscription.remove(); } onNotificationResponse(handler: (response: NotificationResponse) => void): () => void { // Subscribe to notification taps const subscription = myNotificationLib.onNotificationOpened(handler); return () => subscription.remove(); } } ``` # WixNotificationsAdapter (/docs/react-native/adapters/notifications/wix) The `WixNotificationsAdapter` integrates with Wix's react-native-notifications library. ## Installation ```bash npm install react-native-notifications # or bun add react-native-notifications ``` For iOS: ```bash cd ios && pod install ``` ## Usage ```typescript import { WixNotificationsAdapter } from '@teardown/react-native/wix-notifications'; const notifications = new WixNotificationsAdapter(); // Request permissions const granted = await notifications.requestPermissions(); if (granted) { // Get push token const token = await notifications.getToken(); console.log('Push token:', token); } ``` ## Import Path ```typescript import { WixNotificationsAdapter } from '@teardown/react-native/wix-notifications'; ``` ## API ### getToken() Get the device push token: ```typescript const token = await notifications.getToken(); // Returns: device token string or null ``` ### requestPermissions() Request notification permissions: ```typescript const granted = await notifications.requestPermissions(); // Returns: true if granted, false otherwise ``` ### isEnabled() Check if notifications are enabled: ```typescript const enabled = await notifications.isEnabled(); ``` ### onNotification() Subscribe to incoming notifications: ```typescript const unsubscribe = notifications.onNotification((notification) => { console.log('Received:', notification.title); }); // Cleanup unsubscribe(); ``` ### onNotificationResponse() Subscribe to notification opens: ```typescript const unsubscribe = notifications.onNotificationResponse((response) => { console.log('User tapped:', response.notification.title); }); // Cleanup unsubscribe(); ``` ## Configuration ### iOS 1. Enable Push Notifications capability in Xcode 2. Add to `AppDelegate.m`: ```objc #import - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [RNNotifications startMonitorNotifications]; return YES; } - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RNNotifications didFailToRegisterForRemoteNotificationsWithError:error]; } ``` ### Android 1. Add Firebase configuration (google-services.json) 2. Initialize in `MainApplication.java`: ```java import com.wix.reactnativenotifications.RNNotificationsPackage; ``` ## When to Use * Existing projects using react-native-notifications * Projects requiring fine-grained notification control * Cross-platform notification handling ## Requirements * `react-native-notifications` >= 4.0.0 * React Native >= 0.60 # AsyncStorageAdapter (/docs/react-native/adapters/storage/async-storage) The `AsyncStorageAdapter` uses the community AsyncStorage library for persistent storage with broad platform compatibility. ## Installation ```bash npm install @react-native-async-storage/async-storage # or bun add @react-native-async-storage/async-storage ``` For Expo: ```bash npx expo install @react-native-async-storage/async-storage ``` ## Usage ```typescript import { TeardownCore } from '@teardown/react-native'; import { DeviceInfoAdapter } from '@teardown/react-native/adapters/device-info'; import { AsyncStorageAdapter } from '@teardown/react-native/adapters/async-storage'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new AsyncStorageAdapter(), deviceAdapter: new DeviceInfoAdapter(), }); ``` ## Import Path ```typescript import { AsyncStorageAdapter } from '@teardown/react-native/adapters/async-storage'; ``` ## When to Use * Web support needed * Existing AsyncStorage setup in your app * Simpler debugging (plain text storage) * Expo Go compatibility (no dev build needed) ## Trade-offs ### Pros * Broader platform support (iOS, Android, Web) * Works in Expo Go without dev build * Plain text storage (easier debugging) * Well-established, stable API ### Cons * Asynchronous API (slightly slower) * No built-in encryption * Larger bundle size than MMKV ## How It Works AsyncStorage provides a key-value storage API: 1. The adapter wraps AsyncStorage with a synchronous-like interface 2. Uses `preload()` to hydrate data into memory at startup 3. Writes are persisted asynchronously ## Requirements * `@react-native-async-storage/async-storage` >= 1.17.0 * React Native >= 0.60 ## Platform Notes ### iOS * Data stored in NSUserDefaults (small data) or files (large data) * Not encrypted by default ### Android * Data stored in SQLite database * Not encrypted by default ### Web * Uses localStorage * Size limited (\~5MB) ## Encryption Note AsyncStorage does not encrypt data by default. For sensitive data, consider: * Using MMKV instead (encrypted by default) * Encrypting values before storage * Using a separate encrypted storage for secrets # Storage Adapters (/docs/react-native/adapters/storage) Storage adapters handle persistent data storage for the SDK. You must provide a storage adapter when initializing `TeardownCore`. ## Available Adapters | Adapter | Package | Sync/Async | Best For | | -------------------------------------------------------------------------- | ------------------------------------------- | ---------- | ------------------------- | | [`MMKVStorageAdapter`](/docs/react-native/adapters/storage/mmkv) | `react-native-mmkv` | Sync | Performance (recommended) | | [`AsyncStorageAdapter`](/docs/react-native/adapters/storage/async-storage) | `@react-native-async-storage/async-storage` | Async | Compatibility | ## Storage Interface All storage adapters implement: ```typescript interface SupportedStorage { preload(): void; getItem(key: string): string | null; setItem(key: string, value: string): void; removeItem(key: string): void; clear(): void; keys(): string[]; } ``` ## What Gets Stored The SDK stores: | Data | Storage Key | Description | | -------------- | ------------------------- | ------------------------ | | Identity State | `identity:IDENTIFY_STATE` | Session, user ID, token | | Version Status | `version:version_status` | Last known update status | | Device ID | `device:device_id` | Stable device UUID | ## Namespacing Keys are prefixed with org/project IDs to prevent conflicts: ``` teardown:{org_id}:{project_id}:identity:IDENTIFY_STATE teardown:{org_id}:{project_id}:version:version_status teardown:{org_id}:{project_id}:device:device_id ``` ## Custom Adapter Extend `StorageAdapter` for custom implementations: ```typescript import { StorageAdapter, type SupportedStorage } from '@teardown/react-native'; class CustomStorageAdapter extends StorageAdapter { createStorage(storageKey: string): SupportedStorage { return { preload: () => { // Load data into memory (called on init) }, getItem: (key: string) => { return myStorage.get(`${storageKey}:${key}`); }, setItem: (key: string, value: string) => { myStorage.set(`${storageKey}:${key}`, value); }, removeItem: (key: string) => { myStorage.delete(`${storageKey}:${key}`); }, clear: () => { // Clear all keys for this namespace }, keys: () => { // Return all keys for this namespace return []; }, }; } } ``` ## Comparison | Feature | MMKV | AsyncStorage | | ---------------- | ------------- | ----------------- | | Speed | Faster (sync) | Slower (async) | | Encryption | Built-in | No | | Bundle Size | Smaller | Larger | | Debugging | Binary data | Plain text | | Platform Support | iOS, Android | iOS, Android, Web | # MMKVStorageAdapter (/docs/react-native/adapters/storage/mmkv) The `MMKVStorageAdapter` uses MMKV for high-performance, encrypted storage. **Recommended for most apps.** ## Installation ```bash npm install react-native-mmkv # or bun add react-native-mmkv ``` For iOS, run pod install: ```bash cd ios && pod install ``` ## Usage ```typescript import { TeardownCore } from '@teardown/react-native'; import { ExpoDeviceAdapter } from '@teardown/react-native/adapters/expo'; import { MMKVStorageAdapter } from '@teardown/react-native/adapters/mmkv'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); ``` ## Import Path ```typescript import { MMKVStorageAdapter } from '@teardown/react-native/adapters/mmkv'; ``` ## Benefits * **Synchronous** - No async/await needed for reads/writes * **Encrypted** - Data encrypted by default * **Fast** - Memory-mapped for optimal performance * **Small** - Minimal bundle size impact * **Reliable** - Used by WeChat with 1B+ users ## How It Works MMKV uses memory-mapped files for storage: 1. Data is written to memory 2. OS flushes to disk asynchronously 3. Reads are instant (from memory) 4. Crash-safe with journaling ## Requirements * `react-native-mmkv` >= 2.0.0 * React Native >= 0.71 ## Platform Notes ### iOS * Requires `pod install` * Data stored in app's sandboxed Documents directory * Encrypted with iOS Keychain ### Android * Works out of the box * Data stored in app's private directory * Encrypted with Android KeyStore ## Expo Compatibility For Expo managed workflow, you need a development build: ```bash npx expo install react-native-mmkv npx expo prebuild ``` Or use EAS Build for production. # Advanced Usage (/docs/react-native/advanced) ## Multiple Environments Configure different credentials per environment: ```typescript const getConfig = () => { if (__DEV__) { return { org_id: 'dev-org-id', project_id: 'dev-project-id', api_key: 'dev-api-key', }; } return { org_id: 'prod-org-id', project_id: 'prod-project-id', api_key: 'prod-api-key', }; }; export const teardown = new TeardownCore({ ...getConfig(), storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); ``` ## Disable Force Update Checking For debugging or testing: ```typescript const teardown = new TeardownCore({ // ... forceUpdate: { checkIntervalMs: -1, // Disable automatic checking }, }); ``` ## Custom Version Check Interval Check more or less frequently: ```typescript const teardown = new TeardownCore({ // ... forceUpdate: { checkIntervalMs: 60_000, // Check every minute checkOnForeground: true, }, }); ``` ## Manual Version Checking Trigger version check programmatically: ```typescript const { core } = useTeardown(); const checkForUpdates = async () => { const result = await core.identity.refresh(); if (result.success) { // Version status will update via subscription console.log('Version checked'); } }; ``` ## Offline Support The SDK handles offline scenarios gracefully: 1. **Cached session** - Identity state persists across restarts 2. **Cached version status** - Last known status is preserved 3. **Retry on foreground** - Re-checks when connectivity restored ```typescript const session = useSession(); // Works offline - returns cached session if (session) { console.log('User:', session.user_id); } ``` ## Pre-warming Initialize SDK before app is fully loaded: ```typescript // In app entry point (before React renders) import { teardown } from './lib/teardown'; // SDK starts initializing immediately // By the time UI renders, it may already be ready ``` ## Error Boundaries Handle SDK errors gracefully: ```tsx import { ErrorBoundary } from 'react-error-boundary'; function App() { return ( }> ); } ``` ## Testing ### Mock the SDK ```typescript // __mocks__/@teardown/react-native.ts export const TeardownProvider = ({ children }) => children; export const useTeardown = () => ({ core: { identity: { identify: jest.fn().mockResolvedValue({ success: true, data: mockSession }), reset: jest.fn(), getSessionState: () => mockSession, }, forceUpdate: { getVersionStatus: () => ({ type: 'up_to_date' }), }, }, }); export const useSession = () => mockSession; export const useForceUpdate = () => ({ versionStatus: { type: 'up_to_date' }, isUpdateRequired: false, isUpdateRecommended: false, isUpdateAvailable: false, }); ``` ### Test Components ```typescript import { render } from '@testing-library/react-native'; test('shows login when not identified', () => { jest.spyOn(hooks, 'useSession').mockReturnValue(null); const { getByText } = render(); expect(getByText('Login')).toBeTruthy(); }); ``` ## Debugging ### Enable Verbose Logging ```typescript teardown.setLogLevel('verbose'); ``` ### Inspect State ```typescript const { core } = useTeardown(); console.log('Identity state:', core.identity.getIdentifyState()); console.log('Version status:', core.forceUpdate.getVersionStatus()); console.log('Device ID:', await core.device.getDeviceId()); ``` ### Clear All Data For debugging or logout: ```typescript core.identity.reset(); // This clears session data and device association ``` ## Performance ### Minimize Re-renders Use specific hooks instead of broad subscriptions: ```tsx // Good - only re-renders on session change const session = useSession(); // Avoid - accessing core gives no reactivity const { core } = useTeardown(); const session = core.identity.getSessionState(); // Not reactive! ``` ### Lazy Initialization Defer SDK init until needed: ```typescript let teardownInstance: TeardownCore | null = null; export const getTeardown = () => { if (!teardownInstance) { teardownInstance = new TeardownCore({ /* ... */ }); } return teardownInstance; }; ``` ## Cleanup Properly cleanup on app shutdown: ```typescript import { AppState } from 'react-native'; useEffect(() => { const subscription = AppState.addEventListener('change', (state) => { if (state === 'background') { // SDK persists state automatically, no action needed } }); return () => { subscription.remove(); teardown.shutdown(); }; }, []); ``` ## TypeScript ### Strict Types All SDK types are exported: ```typescript import type { TeardownCore, TeardownCoreOptions, Session, IdentifyState, VersionStatus, Persona, AsyncResult, } from '@teardown/react-native'; ``` ### Type Guards ```typescript function handleIdentifyState(state: IdentifyState) { switch (state.type) { case 'unidentified': // state is UnidentifiedSessionState break; case 'identifying': // state is IdentifyingSessionState break; case 'identified': // state is IdentifiedSessionState console.log(state.session.user_id); break; } } ``` # API Reference (/docs/react-native/api-reference) ## TeardownCore The main SDK class that orchestrates all clients. ### Constructor ```typescript new TeardownCore(options: TeardownCoreOptions) ``` ### TeardownCoreOptions | Property | Type | Required | Description | | ---------------- | -------------------------- | -------- | ------------------------------ | | `org_id` | `string` | Yes | Organization ID from dashboard | | `project_id` | `string` | Yes | Project ID from dashboard | | `api_key` | `string` | Yes | API key from dashboard | | `storageAdapter` | `StorageAdapter` | Yes | Storage adapter instance | | `deviceAdapter` | `DeviceInfoAdapter` | Yes | Device info adapter instance | | `forceUpdate` | `ForceUpdateClientOptions` | No | Force update configuration | ### Methods | Method | Returns | Description | | -------------------- | ------- | --------------------- | | `setLogLevel(level)` | `void` | Set logging verbosity | | `shutdown()` | `void` | Cleanup SDK resources | ### Properties | Property | Type | Description | | ------------- | ------------------- | -------------------------- | | `api` | `ApiClient` | Backend API client | | `device` | `DeviceClient` | Device information client | | `identity` | `IdentityClient` | Identity management client | | `forceUpdate` | `ForceUpdateClient` | Version checking client | *** ## IdentityClient Manages user and device identification. ### Methods | Method | Returns | Description | | --------------------------------- | --------------------------- | -------------------------- | | `identify(persona?)` | `AsyncResult` | Identify user/device | | `refresh()` | `AsyncResult` | Re-identify current user | | `reset()` | `void` | Clear session data | | `getIdentifyState()` | `IdentifyState` | Get current state | | `getSessionState()` | `Session \| null` | Get current session | | `onIdentifyStateChange(listener)` | `() => void` | Subscribe to state changes | | `shutdown()` | `void` | Cleanup listeners | ### Persona ```typescript interface Persona { user_id?: string; email?: string; name?: string; } ``` ### Session ```typescript interface Session { session_id: string; device_id: string; user_id: string; token: string; } ``` ### IdentifyState ```typescript type IdentifyState = | { type: 'unidentified' } | { type: 'identifying' } | { type: 'identified'; session: Session; version_info: VersionInfo }; ``` *** ## ForceUpdateClient Manages version checking and force updates. ### Methods | Method | Returns | Description | | --------------------------------- | --------------- | --------------------------- | | `getVersionStatus()` | `VersionStatus` | Get current version status | | `onVersionStatusChange(listener)` | `() => void` | Subscribe to status changes | | `shutdown()` | `void` | Cleanup listeners | ### ForceUpdateClientOptions | Property | Type | Default | Description | | ------------------------- | --------- | -------- | ---------------------------------------------------------------- | | `checkIntervalMs` | `number` | `300000` | Min time between checks. `0` = every foreground, `-1` = disabled | | `checkOnForeground` | `boolean` | `true` | Check when app foregrounds | | `identifyAnonymousDevice` | `boolean` | `false` | Check when not identified | ### VersionStatus ```typescript type VersionStatus = | { type: 'initializing' } | { type: 'checking' } | { type: 'up_to_date' } | { type: 'update_available' } | { type: 'update_recommended' } | { type: 'update_required' } | { type: 'disabled' }; ``` *** ## DeviceClient Collects device information. ### Methods | Method | Returns | Description | | ----------------- | --------------------- | ---------------------- | | `getDeviceId()` | `Promise` | Get stable device UUID | | `getDeviceInfo()` | `Promise` | Get full device info | ### DeviceInfo ```typescript interface DeviceInfo { timestamp: string; os: OSInfo; hardware: HardwareInfo; application: ApplicationInfo; } ``` *** ## StorageAdapter Abstract class for storage implementations. ### Methods | Method | Returns | Description | | -------------------- | ------------------ | ------------------------- | | `createStorage(key)` | `SupportedStorage` | Create namespaced storage | ### SupportedStorage ```typescript interface SupportedStorage { preload(): void; getItem(key: string): string | null; setItem(key: string, value: string): void; removeItem(key: string): void; clear(): void; keys(): string[]; } ``` *** ## DeviceInfoAdapter Interface for device information adapters. ### Properties | Property | Type | Description | | ----------------- | ----------------- | --------------------- | | `applicationInfo` | `ApplicationInfo` | App version info | | `hardwareInfo` | `HardwareInfo` | Device hardware info | | `osInfo` | `OSInfo` | Operating system info | ### ApplicationInfo ```typescript interface ApplicationInfo { version: string; buildNumber: string; bundleId: string; } ``` ### HardwareInfo ```typescript interface HardwareInfo { deviceName: string; brand: string; deviceType: string; } ``` ### OSInfo ```typescript interface OSInfo { osName: string; osVersion: string; } ``` *** ## AsyncResult Type-safe result type for async operations. ```typescript type AsyncResult = | { success: true; data: T } | { success: false; error: string }; ``` ### Usage ```typescript const result = await core.identity.identify({ user_id: '123' }); if (result.success) { console.log(result.data.session_id); } else { console.error(result.error); } ``` # Core Concepts (/docs/react-native/core-concepts) ## Architecture Overview The Teardown SDK is built around a central `TeardownCore` class that orchestrates multiple specialized clients: ``` TeardownCore ├── IdentityClient # User/device identification ├── ForceUpdateClient # Version checking ├── DeviceClient # Device information ├── StorageClient # Persistent storage ├── ApiClient # Backend communication └── LoggingClient # Structured logging ``` ## Initialization Flow When you create a `TeardownCore` instance, the following happens: 1. **Storage Hydration** - Storage adapters load persisted state 2. **Identity Initialization** - Loads cached session, then identifies with backend 3. **Force Update Setup** - Subscribes to identity events for version checking 4. **Ready State** - SDK is fully operational ```typescript const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); // Initialization happens automatically in the background ``` ## State Management The SDK uses an event-driven architecture with discriminated unions for type-safe state: ### Identity State ```typescript type IdentifyState = | { type: 'unidentified' } | { type: 'identifying' } | { type: 'identified'; session: Session; version_info: VersionInfo }; ``` ### Version Status ```typescript type VersionStatus = | { type: 'initializing' } | { type: 'checking' } | { type: 'up_to_date' } | { type: 'update_available' } | { type: 'update_recommended' } | { type: 'update_required' } | { type: 'disabled' }; ``` ## Adapter Pattern The SDK uses adapters to abstract platform-specific functionality: ### Storage Adapters Handle persistent data storage with a consistent interface: ```typescript interface SupportedStorage { preload(): void; getItem(key: string): string | null; setItem(key: string, value: string): void; removeItem(key: string): void; clear(): void; keys(): string[]; } ``` ### Device Adapters Provide device and app information: ```typescript interface DeviceInfoAdapter { applicationInfo: ApplicationInfo; // version, buildNumber, bundleId hardwareInfo: HardwareInfo; // deviceName, brand, deviceType osInfo: OSInfo; // osName, osVersion } ``` ## React Integration The SDK provides React primitives for seamless integration: ### TeardownProvider Context provider that makes the SDK available throughout your app: ```tsx ``` ### Hooks Reactive hooks that subscribe to SDK state changes: * `useTeardown()` - Access the core instance * `useSession()` - Get current session (reactive) * `useForceUpdate()` - Get version status (reactive) ## Persistence The SDK automatically persists: * **Session data** - Device ID, user ID, token * **Version status** - Last known update state * **Device ID** - Stable device identifier State is restored on app restart, providing offline-first functionality. ## Error Handling All async operations return `AsyncResult` for type-safe error handling: ```typescript type AsyncResult = | { success: true; data: T } | { success: false; error: string }; const result = await core.identity.identify({ user_id: '123' }); if (result.success) { console.log(result.data.session_id); } else { console.error(result.error); } ``` ## Logging The SDK includes a structured logging system with configurable levels: ```typescript teardown.setLogLevel('verbose'); // none | error | warn | info | verbose ``` Each client logs with a prefix for easy filtering: * `[Teardown:IdentityClient]` * `[Teardown:ForceUpdateClient]` * `[Teardown:DeviceClient]` # Force Updates (/docs/react-native/force-updates) The Force Update client automatically checks your app version against your configured release policy and can require users to update. ## Overview Force updates work by: 1. Checking app version on identify 2. Checking again when app returns to foreground 3. Emitting status changes your app can respond to ## Version Status ```typescript type VersionStatus = | { type: 'initializing' } // SDK starting up | { type: 'checking' } // Version check in progress | { type: 'up_to_date' } // Current version is valid | { type: 'update_available' } // Optional update exists | { type: 'update_recommended' } // Recommended update exists | { type: 'update_required' } // Must update to continue | { type: 'disabled' } // Version/build has been disabled ``` ## Basic Usage ### Using the Hook ```typescript import { useForceUpdate } from '@teardown/react-native'; function App() { const { versionStatus, isUpdateRequired, isUpdateAvailable, isUpdateRecommended } = useForceUpdate(); if (isUpdateRequired) { return ; } if (isUpdateRecommended) { return ( <> {}} /> ); } return ; } ``` ### Hook Return Values ```typescript interface UseForceUpdateResult { versionStatus: VersionStatus; // Full status object isUpdateAvailable: boolean; // Any update exists (available, recommended, or required) isUpdateRecommended: boolean; // Update is recommended but not required isUpdateRequired: boolean; // Update is mandatory } ``` ## Configuration Configure force update behavior in `TeardownCore`: ```typescript const teardown = new TeardownCore({ // ...other options forceUpdate: { checkIntervalMs: 300_000, // 5 minutes (default) checkOnForeground: true, // Check when app foregrounds (default: true) identifyAnonymousDevice: false, // Check even when not identified (default: false) }, }); ``` ### Options | Option | Type | Default | Description | | ------------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | | `checkIntervalMs` | number | 300000 | Minimum time between version checks. Use `0` for every foreground, `-1` to disable. Values below 30s are clamped to 30s. | | `checkOnForeground` | boolean | true | Check version when app returns to foreground | | `identifyAnonymousDevice` | boolean | false | Check version even when user is not identified | ## Direct API Access ### Get Current Status ```typescript const status = core.forceUpdate.getVersionStatus(); ``` ### Subscribe to Changes ```typescript const unsubscribe = core.forceUpdate.onVersionStatusChange((status) => { if (status.type === 'update_required') { // Show force update modal } }); // Cleanup unsubscribe(); ``` ## Implementation Patterns ### Force Update Modal ```tsx function ForceUpdateModal() { const handleUpdate = () => { // Open app store Linking.openURL('https://apps.apple.com/app/your-app'); }; return ( Update Required Please update to continue using the app.