# Mobana Mobana is a mobile analytics platform for iOS and Android apps. It provides install attribution, conversion tracking, in-app flows, and deeplinking. 3-10x more affordable than enterprise MMPs (Branch, Adjust, AppsFlyer, Kochava). Website: https://mobana.ai SDK: `@mobana/react-native-sdk` (npm) — React Native SDK available today Native iOS and Android SDKs are on the roadmap. REST API available for custom integrations on any platform (see /docs/api/*). ## Core Solutions ### Attribution & Deeplinking - Track which ads, campaigns, and referrals drive app installs - Full UTM parameter support (source, medium, campaign, content, term) - Web referrer domain tracking: automatically captures the referring domain (e.g., "facebook.com") — domain only, no full URL, GDPR-friendly - Custom deeplink data: pass arbitrary JSON through attribution links to the app (promo codes, referral IDs, routing data) - Matching: probabilistic (IP, timezone, screen size, language) + deterministic (Android Install Referrer API) - Confidence scoring on every match (0.0–1.0); only matches above 70% returned by default - Attribution records auto-deleted after 6 hours (privacy by design) - Custom domain support: proxy tracking links through your own domain (bypasses ad blockers, cleaner URLs) - Real-time analytics: UTM breakdowns, referrer domain breakdowns, platform distribution, geographic data, conversion rates - Privacy-first: no device IDs collected, no persistent fingerprinting, GDPR-compliant - Billing: only successful attribution matches are billable (clicks and failed matches are free) ### Conversion Tracking - Track post-install events (signup, purchase, subscription, level completion, etc.) - Revenue attribution: attach monetary values to see actual revenue per campaign/source - Conversions auto-linked to original install attribution via install ID - Configurable conversion types: repeatable or one-time per install - Conversion funnel analysis (see below) - Attributed vs organic segmentation: compare how attributed users convert vs organic - Offline queueing: events queued locally and sent when connection restores - Conversion tracking is FREE with all plans (no additional cost) **Conversion funnels:** - Interactive funnel diagrams visualize complex multi-step user journeys from install to revenue - Configurable time windows: 6 hours, 24 hours, 7 days, 14 days, or 30 days - See exactly where users drop off between steps (e.g. install → signup → purchase) - Compare conversion rates between stages and attribute revenue to each step - Spot bottlenecks and optimize the path from install to conversion - Conversion velocity: time-to-conversion distribution curves per campaign (identify fast converters vs slow burners) ### In-App Flows (Dynamic Remote Flows) - Full-screen HTML/CSS/JS experiences displayed in a native WebView - Update flow content without app store releases — changes go live instantly - Use cases: onboarding, permission prompts, pre-paywall/upsell screens, surveys, NPS, feature announcements, promotional campaigns - Agentic AI flow builder: describe flows in plain English, AI generates complete HTML/CSS/JS - Templates, visual editor, or full custom code control - Inline mock simulator for real-time preview - Version history with instant rollback - A/B testing: run multiple flow versions in parallel, compare completion rates (Pro plan) - Flow journey maps (see below) - Native bridge API inside flows: haptics, permissions (notifications, ATT, location), sounds, URLs, app reviews, event tracking - Personalization: customize flow content based on attribution data, custom params from the app, platform, color scheme - Bi-directional communication: flows send data/events back to the app; app passes params into flows - Flows can also make API calls to external services **Flow journey maps:** - Visualize every path users take through multi-step flows - Journey diagram shows engagement at each step and where users drop off or dismiss - Compare flow versions side-by-side (A/B testing on Pro plan) - Track completion rates per step to identify friction points - Link flow completions to downstream conversions via sessionId for end-to-end funnel visibility ## Dashboard & Analytics - Real-time dashboards for attribution, conversions, and flows - Filter by source, campaign, medium, country, platform, date range - Revenue tracking and campaign ROI analysis - Interactive conversion funnel diagrams with configurable time windows (6h to 30d) - Flow analytics: views, completions, dismissals, completion rates, per-step journey maps, version comparison - Conversion velocity distribution curves (time from install to each conversion event) - Configurable usage caps to control spend on overage ## Technical Architecture - Cloud service + lightweight client SDK + web dashboard - Single SDK for attribution, conversions, and flows - Privacy: GDPR-compliant, consent controls via `setTrackingEnabled()`, no PII stored - Integration: ~5 minutes, 1–4 method calls (`init` → optionally `getAttribution` → `trackConversion` → `startFlow`) - Expo apps require a config plugin: add `"@mobana/react-native-sdk"` to the `plugins` array in `app.config.js` before building (see SDK reference) - WebView-based flows with injected JavaScript bridge for native access - Offline-capable: attribution cached, conversions queued, flows cached locally ## Pricing All plans include the same SDK. Conversion tracking is free on every plan. | Plan | Price | Attributions | Flow Views | AI Credits | Apps | Data Retention | |------|-------|-------------|------------|------------|------|----------------| | Free Trial | $0/mo (3 months) | 2,500/mo | 5,000/mo | 25 | 3 | 30 days | | Starter | $49/mo | 5,000/mo (then $0.01 each) | 10,000/mo (then $0.005 each) | 50 | 5 | 6 months | | Pro | $169/mo | 20,000/mo (then $0.0085 each) | 50,000/mo (then $0.004 each) | 250 | Unlimited | 12 months | | Scale | Custom | 100k+/mo | 250k+/mo | 500 | Unlimited | Unlimited | Pro plan adds: A/B testing for flows, team access, priority support. Scale plan adds: on-premise option, SLA guarantee, dedicated account manager. No credit card required to start. Usage caps configurable on all paid plans. ## Documentation & AI Context - Human documentation: https://mobana.ai/docs - Full docs index: https://mobana.ai/docs/index.json - AI-optimized SDK reference: https://mobana.ai/docs/ai-context?section=sdk - AI-optimized Flow Bridge reference: https://mobana.ai/docs/ai-context?section=flowBridge - All sections combined: https://mobana.ai/docs/ai-context --- # Mobana React Native SDK Reference Mobana is a lightweight React Native SDK for mobile attribution, conversion tracking, and in-app flows. Package: `@mobana/react-native-sdk` For non-React-Native platforms, use the REST API directly (see /docs/api/*). EXPO REQUIREMENT: If the project uses Expo, you MUST add `"@mobana/react-native-sdk"` to the `plugins` array in `app.config.js` (or `app.json`) before building. Without this config plugin, the Android Install Referrer native module will not be linked and deterministic attribution (confidence: 1.0) will not work. This step is required regardless of whether EAS Build or local builds are used. Does not work in Expo Go — use `expo-dev-client`. ## Integration Pattern 1. Install: `npm install @mobana/react-native-sdk @react-native-async-storage/async-storage` 2. **Expo only:** add the config plugin to `app.config.js`. If your flows use permission prompts, install `react-native-permissions` AND pass permissions to the Mobana plugin (the plugin handles native setup automatically — Podfile, Info.plist, AndroidManifest — but you still need the npm package installed): ```js // app.config.js — attribution-only or flows without permissions export default { expo: { plugins: ["@mobana/react-native-sdk"] } }; // app.config.js — flows with permission prompts export default { expo: { plugins: [["@mobana/react-native-sdk", { permissions: ["Notifications", "AppTrackingTransparency", "LocationWhenInUse"] }]] } }; ``` 3. Call `init()` once on app start — attribution is fetched automatically in the background by default 4. Optionally call `getAttribution()` when you need the attribution data (UTM params, deeplink data) 5. Call `trackConversion()` when users complete key actions (signup, purchase, etc.) 6. For flows: wrap your app in `` and call `startFlow()` Peer dependencies: - `@react-native-async-storage/async-storage` — required (for caching) - `react-native-webview` — optional, required for flows - `react-native-safe-area-context` — optional, improves safe area insets in flows - `react-native-permissions` — optional, for permission prompts in flows (both bare RN and Expo — on Expo, the Mobana config plugin handles native setup automatically, but the package must still be installed) - `react-native-haptic-feedback` — optional, for haptics in flows - `react-native-in-app-review` — optional, for app review prompts in flows - `react-native-geolocation-service` — optional, for location services in flows ## Methods ### init() ```typescript Mobana.init(config: MobanaConfig): Promise ``` Initialize the SDK. Must be called before any other method. | Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | appId | string | yes | — | App ID from the Dashboard | | appKey | string | yes | — | App key from Dashboard (sent as X-App-Key; regeneratable) | | endpoint | string | no | https://{appId}.mobana.ai | Custom API endpoint for domain proxying | | enableTracking | boolean | no | true | Set false to disable attribution and tracking (GDPR). Flows (startFlow/prefetchFlow) still work — no installId is sent so no trace is left server-side. | | autoAttribute | boolean | no | true | Fetch attribution in the background on init. Set false to delay until explicit getAttribution() call (e.g., GDPR consent first) | | debug | boolean | no | false | Enable debug logging | Minimal Example: ```typescript import { Mobana } from '@mobana/react-native-sdk'; Mobana.init({ appId: 'a1b2c3d4', appKey: 'your-32-char-hex-app-key' }); ``` Expo: also add the config plugin to `app.config.js` before building (required for Android Install Referrer). Attribution-only (or flows without permission prompts): ```js // app.config.js export default { expo: { plugins: ["@mobana/react-native-sdk"], }, }; ``` With flows that use permission prompts (notifications, location, ATT) — install `react-native-permissions` (`npx expo install react-native-permissions`) and pass permissions to the Mobana plugin (the plugin handles the native Podfile/Info.plist/AndroidManifest setup automatically): ```js // app.config.js export default { expo: { plugins: [ ["@mobana/react-native-sdk", { "permissions": [ "Notifications", "AppTrackingTransparency", "LocationWhenInUse", "LocationAlways" ] }] ], }, }; ``` Only include permissions your flows actually use. Valid values: `Notifications`, `AppTrackingTransparency`, `LocationWhenInUse`, `LocationAlways`. iOS usage description strings (Expo): The plugin injects default strings into Info.plist for each permission. The defaults are generic — override them in `expo.ios.infoPlist` to avoid App Store rejection, especially for ATT: ```js // app.config.js export default { expo: { ios: { infoPlist: { NSUserTrackingUsageDescription: "We use this to show you relevant content and measure ad effectiveness.", NSLocationWhenInUseUsageDescription: "We use your location to provide location-relevant features.", NSLocationAlwaysAndWhenInUseUsageDescription: "We use your location in the background to provide location-relevant features.", } }, plugins: [["@mobana/react-native-sdk", { permissions: ["AppTrackingTransparency"] }]] } }; ``` Plugin defaults (used if you don't set your own): NSUserTrackingUsageDescription → "This identifier will be used to measure effectiveness of our campaigns and deliver relevant content to you." | NSLocationWhenInUseUsageDescription → "This app needs access to your location." | NSLocationAlwaysAndWhenInUseUsageDescription → "This app needs access to your location in the background." WARNING: Apple frequently rejects apps with the generic NSUserTrackingUsageDescription default. Always customize it. iOS usage description strings (bare RN): Managed entirely by the developer. Add them directly to `ios/YourApp/Info.plist`. The SDK does not inject any defaults. Notes: - Attribution is fetched automatically in the background (autoAttribute: true by default). By the time you call getAttribution(), the result is usually already cached. - Conversions tracked before init are queued and sent after init completes. - Idempotent — calling multiple times is safe, updates config. - Call as early as possible (root component, app entry point). No need to await. ### getAttribution() ```typescript Mobana.getAttribution(options?: { timeout?: number }): Promise> ``` Retrieve attribution data for this install. Never throws. | Param | Type | Default | Description | |-------|------|---------|-------------| | timeout | number | 10000 | Timeout in milliseconds | Returns `AttributionResult` object: | Field | Type | Description | |-------|------|-------------| | status | 'matched' | 'no_match' | 'error' | 'matched' = attribution found. 'no_match' = organic install. 'error' = network/server failure or SDK misconfiguration (check error.type) | | attribution | Attribution | null | Attribution data. Only present when status is 'matched' | | error | { type, status? } | undefined | Error details when status is 'error'. type: 'network' | 'timeout' | 'server' | 'sdk_not_configured' | 'tracking_disabled' | 'unknown' | `attribution` fields (when status === 'matched'): | Field | Type | Description | |-------|------|-------------| | utm_source | string? | Traffic source (facebook, google, tiktok, etc.) | | utm_medium | string? | Marketing medium (cpc, social, email, etc.) | | utm_campaign | string? | Campaign name | | utm_content | string? | Ad content identifier | | utm_term | string? | Search keywords | | referrer_domain | string? | Referring domain (e.g., "facebook.com") | | data | T? | Custom deeplink data from redirect URL | | confidence | number | Match confidence 0.0–1.0. 1.0 = deterministic (Android Install Referrer) | Example: ```typescript const { status, attribution } = await Mobana.getAttribution(); if (attribution) { console.log('Source:', attribution.utm_source); } else if (status === 'error') { // Log to Sentry, Datadog, etc. // SDK will retry next call, or you can schedule retry } ``` Notes: - Results are cached in memory and AsyncStorage. Multiple calls are cheap. - With autoAttribute: true (default), result is usually already cached by the time you call getAttribution(). - On network/server/unknown errors, result is NOT cached — the next call retries automatically. sdk_not_configured and tracking_disabled errors are also not cached. - Use TypeScript generics for type-safe custom data: `getAttribution()` ### trackConversion() ```typescript Mobana.trackConversion(name: string, value?: number, flowSessionId?: string): Promise ``` Track a post-install conversion event linked to attribution. | Param | Type | Required | Description | |-------|------|----------|-------------| | name | string | yes | Conversion type (must be configured in Dashboard). Use snake_case. | | value | number | no | Revenue value (e.g. purchase amount) | | flowSessionId | string | no | Session ID from startFlow() result, for funnel analysis | Example: ```typescript Mobana.trackConversion('purchase', 49.99); Mobana.trackConversion('signup'); // After a flow: Mobana.trackConversion('purchase', 49.99, result.sessionId); ``` Notes: - Never throws — silently handles errors. - Only tracked for attributed installs (organic installs are ignored). - Offline-safe: queued in AsyncStorage, sent when connection restores. - Conversion types must be configured in Dashboard → App Settings → Conversions. - Conversions feed into the Dashboard's interactive funnel analysis (configurable time windows: 6h to 30d). - Pass `flowSessionId` to link conversions to specific flow presentations for end-to-end funnel visibility. ### startFlow() ```typescript Mobana.startFlow(slug: string, options?: FlowOptions): Promise ``` Display an in-app flow in a full-screen modal. Requires `MobanaProvider`. | Param | Type | Required | Description | |-------|------|----------|-------------| | slug | string | yes | Flow's unique identifier (slug) from the Dashboard | | params | Record | no | Custom data available in flow via `getParams()` | | onEvent | (event: string) => void | no | Callback fired when flow calls `trackEvent()` | | onCallback | (data) => Promise> | no | Async callback when flow calls `requestCallback()`. Allows flow to request app actions (purchases, validation) without closing. | Returns `FlowResult`: | Field | Type | Description | |-------|------|-------------| | completed | boolean | True if user completed the flow | | dismissed | boolean | True if user dismissed the flow | | error | FlowError? | Error code if flow couldn't show | | data | Record? | Data passed to `complete(data)` by the flow | | sessionId | string? | Unique session ID for this presentation | | trackEvent | function? | Track events after flow closes | Error codes: `NOT_FOUND`, `PLAN_REQUIRED`, `FLOW_LIMIT_EXCEEDED`, `PROVIDER_NOT_MOUNTED`, `SDK_NOT_CONFIGURED`, `NETWORK_ERROR`, `SERVER_ERROR` Example: ```typescript const result = await Mobana.startFlow('onboarding', { params: { userName: user.name, isPremium: user.isPremium }, onEvent: (eventName) => analytics.track(eventName), }); if (result.completed) { console.log('Completed with data:', result.data); } else if (result.dismissed) { console.log('User dismissed'); } else if (result.error) { console.log('Error:', result.error); } ``` Notes: - Never throws. Check `result.error` for issues. - Flow content is cached locally for offline access and faster display. - Version-aware: automatically fetches updates when flow is republished. ### prefetchFlow() ```typescript Mobana.prefetchFlow(slug: string): Promise ``` Preload flow content for instant display. Optional — flows work fine without prefetching. | Param | Type | Required | Description | |-------|------|----------|-------------| | slug | string | yes | Flow slug from Dashboard | Example: ```typescript // Prefetch during app startup for instant display later await Mobana.init({ appId: 'a1b2c3d4', appKey: 'your-app-key' }); Mobana.prefetchFlow('onboarding'); Mobana.prefetchFlow('push-permission'); ``` Notes: - Silent on failure (network error, flow not found). - Non-blocking — safe to call multiple times without awaiting. - Only prefetch flows the user is likely to see. ### setTrackingEnabled() ```typescript Mobana.setTrackingEnabled(enabled: boolean): void ``` Enable or disable attribution and tracking dynamically. For GDPR consent flows. Flows (startFlow/prefetchFlow) are NOT affected — they work regardless of tracking consent. When tracking disabled: - `getAttribution()` returns `{ status: 'error', error: { type: 'tracking_disabled' } }` - `trackConversion()` is a no-op — conversions are silently dropped, not queued. Any pre-opt-out pending conversions in the local queue are also discarded immediately. - `startFlow()` and `prefetchFlow()` work normally — flow requests omit installId so no session data is recorded - No attribution or conversion network requests made When re-enabled: - Attribution and conversion tracking resume for future events only — no backfill - No installId is generated or stored until tracking is re-enabled Example: ```typescript // User opts out of tracking Mobana.setTrackingEnabled(false); // User opts back in Mobana.setTrackingEnabled(true); ``` ### reset() ```typescript Mobana.reset(): Promise ``` Clear all stored data and generate a new install ID. Use for account deletion or testing. Clears: install ID, attribution cache, conversion queue, flow cache, local data from flows. WARNING: Irreversible. Device will be treated as a new install. ### getInstallId() ```typescript Mobana.getInstallId(): Promise ``` Returns the device's install ID — a random UUID generated on first launch and persisted locally. This is the identifier Mobana uses server-side for attribution and conversion records. Useful for GDPR data access/deletion requests. Can be called before or after `init()`. ## MobanaProvider ```tsx loadingComponent?: ReactNode backgroundColor?: string | { light: string; dark: string } > {children} ``` React provider that enables flow display. Required for `startFlow()`. Not needed if only using attribution and conversions. | Prop | Type | Description | |------|------|-------------| | children | ReactNode | Your app content | | modalProps | Partial | Custom Modal props (animationType, statusBarTranslucent, etc.) | | loadingComponent | ReactNode | Custom loading component. Default: ActivityIndicator | | backgroundColor | string | { light: string; dark: string } | Background color shown while the flow loads. Static string for fixed color; object form auto-switches with system theme. Default: #FFFFFF (light) / #1c1c1e (dark). | Place near root, outside navigation container: ```tsx import { MobanaProvider } from '@mobana/react-native-sdk'; export default function App() { return ( {/* Your screens */} ); } ``` How it works: Provider creates a React Context. When `startFlow()` is called, it opens a full-screen Modal with a WebView that renders the flow's HTML. The JS bridge enables communication. When user completes/dismisses, Modal closes and Promise resolves. --- # Mobana Flow Bridge Reference ## What Are Flows Flows are full-screen HTML experiences displayed inside a WebView in a React Native app. Use them for onboarding, permission requests, feature announcements, surveys, upgrade prompts, and paywalls. The `window.Mobana` bridge object is injected before your scripts run and provides access to native capabilities: haptics, permissions, event tracking, and flow control. ## How to Build Effective Mobile Flows ### Design Directives - One goal per flow. Onboarding OR permissions OR upsell — never all at once. - Lead with value before asking. Explain what the user gets BEFORE requesting a permission, signup, or purchase. - Always provide an exit. Include "Skip", "Maybe later", or a close button. Track skips as events. - Show progress in multi-step flows. Use step indicators (dots, numbers, progress bars). - Use haptic feedback for important interactions: `haptic('light')` for taps, `haptic('success')` for completions, `haptic('error')` for failures, `haptic('selection')` for picker changes. - Every flow MUST call `complete()` or `dismiss()` to close. Otherwise the user is trapped. ### Event Tracking Strategy **Automatic lifecycle events (SDK-managed — NEVER fire these from flow code):** The SDK tracks flow lifecycle at the native layer, outside the WebView: - `__started__` — fires when flow modal is presented (before any flow JS runs) - `__completed__` — fires when `complete()` is called (after flow closes) - `__dismissed__` — fires when `dismiss()` is called (after flow closes) Do NOT add `trackEvent('flow_started')`, `trackEvent('paywall_viewed')`, `trackEvent('flow_dismissed')`, or similar — these duplicate built-in system events. **What to track with `trackEvent()`** — committed decisions where the user moves forward: - Step progression: `step_1_completed`, `step_2_completed` - Confirmed choices: `plan_confirmed`, `interests_confirmed`, `theme_set` - Permission outcomes: `notification_granted`, `notification_denied`, `att_authorized` - Deliberate exits: `onboarding_skipped`, `upgrade_declined` **What NOT to track:** - Flow open/close/dismiss (already __started__/__completed__/__dismissed__) - Toggleable/cyclical interactions (option taps, toggle switches, tab changes) — these create loops in journey diagrams. Track the forward step that commits the selection instead. - Micro-interactions (scrolls, hovers, every tap) - Events that rename system events (e.g. 'paywall_viewed' duplicates __started__) Use snake_case. Ask: "If this event count changes, would I change the flow?" If no, skip it. Complete funnel: `__started__` → [your committed-decision events] → `__completed__` or `__dismissed__` ### Permission Request Pattern The "pre-permission" pattern significantly increases grant rates: 1. Explain the value ("Get daily tips to reach your goals") 2. Show benefits list 3. Primary button: "Enable" → call `requestNotificationPermission()` 4. Secondary button: "Maybe Later" → track skip event, continue flow 5. Handle both granted and denied gracefully — never block on denial ### Personalization - Use `getParams()` for data passed from the app (user name, subscription state, feature flags). - Use `getAttribution()` for campaign-specific content (show premium offer for premium_promo campaign). - Use `getPlatform()` for platform-specific UI (ATT explanation on iOS only). ## SDK-Injected Defaults The SDK automatically applies these to every flow (do NOT duplicate in flow code): **Viewport:** The SDK ensures `viewport-fit=cover` is present on the viewport meta tag for edge-to-edge rendering on iOS. You don't need to add it yourself. **CSS resets** (injected **before** flow CSS, so flows can override any of them): ```css /* SDK reset (auto-injected, do NOT duplicate in flow code) */ *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; } body { -webkit-font-smoothing: antialiased; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; overflow: hidden; } ``` What this means for flow code: - No need for a CSS reset or viewport-fit=cover — already applied - Body does not scroll by default. For scrollable content, use a scroll container: `.content { overflow-y: auto; height: 100vh; }` - Text is not selectable by default (native-app feel). To allow selection in specific areas: `.selectable { user-select: text; }` - To override any reset, just set the property in your flow CSS (it loads after the reset) ## HTML Structure Every flow is a single HTML file with this structure: ```html
``` ## CSS Variables Injected by the bridge onto `:root` **after** flow CSS (SDK values take precedence). Use these instead of hardcoded values. | Variable | Description | Example | |----------|-------------|---------| | --safe-area-top | Top inset (status bar, notch, Dynamic Island) | 47px | | --safe-area-bottom | Bottom inset (home indicator) | 34px | | --safe-area-left | Left inset | 0px | | --safe-area-right | Right inset | 0px | | --screen-width | Screen width in points | 393px | | --screen-height | Screen height in points | 852px | | --color-scheme | "light" or "dark" (also sets color-scheme property) | light | Dark mode approaches: - `light-dark(#fff, #1a1a1a)` — modern CSS function (recommended) - `@media (prefers-color-scheme: dark) { ... }` - `color-scheme: var(--color-scheme)` — enables native form styling Safe area usage: - Container padding: `padding-top: calc(var(--safe-area-top) + 16px)` - Fixed header: `padding-top: calc(var(--safe-area-top) + 16px)` - Fixed bottom button: `padding-bottom: calc(var(--safe-area-bottom) + 16px)` ## Bridge API Reference ### Data Access ``` Mobana.getAttribution(): Attribution | null ``` Returns: `{ utm_source?, utm_medium?, utm_campaign?, utm_content?, utm_term?, referrer_domain?, data?, confidence }` Returns null for organic installs. Synchronous. ``` Mobana.getParams(): Record ``` Custom params passed from app via `startFlow(slug, { params: {...} })`. Use for personalization. ``` Mobana.getInstallId(): string Mobana.getPlatform(): 'ios' | 'android' Mobana.getColorScheme(): 'light' | 'dark' ``` ``` Mobana.getSafeArea(): { top, bottom, left, right, width, height } ``` Values in points. Prefer CSS variables for layout. ``` Mobana.setLocalData(key: string, value: any): void Mobana.getLocalData(key: string): any ``` Persists in AsyncStorage across flow sessions. Cleared on `reset()` or app uninstall. Use case: resume onboarding where user left off. ### Flow Control ``` Mobana.complete(data?: Record): void ``` Close flow successfully. Resolves `startFlow()` with `{ completed: true, data }`. Automatically tracks `__completed__` system event. ``` Mobana.dismiss(): void ``` Close flow as dismissed. Resolves `startFlow()` with `{ dismissed: true }`. Automatically tracks `__dismissed__` system event. ``` Mobana.requestCallback(data?: Record, options?: { timeout?: number }): Promise> ``` Request the app to perform an async action and return a result. The flow stays open while the app processes. Requires `onCallback` to be provided when starting the flow via `startFlow()`. - `data`: arbitrary data sent to the app's `onCallback` handler - `options.timeout`: timeout in seconds (default: 300). Promise rejects on timeout. - Rejects if no `onCallback` handler was provided, if the handler throws, or on timeout. - Use case: trigger purchases, validate promo codes, fetch app-specific data — all without closing the flow. - The flow is responsible for its own loading UI while awaiting the result. Example (flow side): ```javascript try { const result = await Mobana.requestCallback( { action: 'purchase', planId: 'premium' }, { timeout: 120 } ); if (result.success) Mobana.complete({ purchased: true }); } catch (e) { // timeout, no handler, or handler error } ``` Example (app side): ```typescript await Mobana.startFlow('paywall', { onCallback: async (data) => { const purchase = await purchaseManager.buy(data.planId); return { success: purchase.success }; }, }); ``` ### Event Tracking ``` Mobana.trackEvent(name: string): void ``` Track custom event for analytics. Sent to server and to app's `onEvent` callback. Use snake_case: `step_1_completed`, `notification_granted`, `plan_selected`. Do NOT use names starting with `__` (reserved for system events). System events (tracked automatically by the SDK at the native layer — NEVER fire from flow code): - `__started__` — fires when flow modal is presented (before flow JS runs) - `__completed__` — fires when `complete()` is called - `__dismissed__` — fires when `dismiss()` is called Do NOT add trackEvent calls that duplicate these (e.g. 'paywall_viewed', 'flow_dismissed', 'flow_started'). ### Permissions ``` Mobana.requestNotificationPermission(): Promise Mobana.checkNotificationPermission(): Promise<{ status: 'granted'|'denied'|'blocked'|'unavailable', granted: boolean, settings? }> ``` ``` Mobana.requestATTPermission(): Promise<'authorized'|'denied'|'not-determined'|'restricted'> Mobana.checkATTPermission(): Promise ``` iOS only. On Android, both return `'authorized'` immediately. ``` Mobana.requestLocationPermission(options?: { precision: 'precise'|'coarse' }): Promise<'granted'|'denied'|'blocked'> Mobana.requestBackgroundLocationPermission(): Promise ``` Background requires foreground permission first. ``` Mobana.getLocationPermissionStatus(): Promise<{ foreground, background, precision }> Mobana.getCurrentLocation(): Promise<{ latitude, longitude, accuracy, altitude?, heading?, speed?, timestamp }> ``` ``` Mobana.openSettings(): void ``` Open app settings page. Use when permission status is `blocked`. ### Native Utilities ``` Mobana.haptic(style: 'light'|'medium'|'heavy'|'success'|'warning'|'error'|'selection'): void ``` Requires `react-native-haptic-feedback`. Falls back to Vibration API. ``` Mobana.playSound(url: string, options?: { volume?, loop?, onEnd?, onError? }): { isPlaying: boolean, stop(): void } ``` Accepts `https://` URLs or `data:audio/mp3;base64,...` data URLs. Keep sounds short (<100KB for base64). ``` Mobana.openURL(url: string): void ``` Opens in device default browser. ``` Mobana.requestAppReview(): void ``` Shows native app review dialog. WARNING: This COMPLETES the flow first due to iOS StoreKit limitation (modal must close before review dialog). Use as the final action only. Requires `react-native-in-app-review`. If unavailable, flow completes without review dialog.