@layers/react-native
v2.1.0
Published
Layers Analytics React Native SDK — thin wrapper over Rust core via WASM
Readme
Layers React Native SDK
@layers/react-native is the Layers analytics SDK for React Native apps. It provides event tracking, screen tracking, user identification, App Tracking Transparency (ATT), SKAdNetwork (SKAN), deep link handling, clipboard-based deferred deep links, consent management, and automatic lifecycle and connectivity handling.
Use this package for bare React Native projects. For Expo managed workflow projects, use @layers/expo instead.
Requirements
- React Native 0.70.0+
- React 18.0+
- iOS 14.0+ / Android API 21+
Installation
npm install @layers/react-native
# or
yarn add @layers/react-nativeRecommended Peer Dependencies
npm install @react-native-async-storage/async-storage @react-native-community/netinfoThese are optional but strongly recommended:
- @react-native-async-storage/async-storage (>=1.21.0) -- Enables persistent event storage across app restarts. Without it, events are stored in memory only.
- @react-native-community/netinfo (>=11.0.0) -- Enables automatic flush when the device reconnects to the network.
- @react-native-clipboard/clipboard (>=1.13.0) -- Enables clipboard attribution for iOS deferred deep links.
iOS Setup
cd ios && pod installAdd NSUserTrackingUsageDescription to your Info.plist if you plan to use ATT.
Quick Start
import { LayersReactNative } from '@layers/react-native';
// Create and initialize
const layers = new LayersReactNative({
appId: 'your-app-id',
environment: 'production'
});
await layers.init();
// Track events
layers.track('button_tapped', { button_name: 'signup' });
// Track screen views
layers.screen('Home');
// Identify users
layers.setAppUserId('user_123');Configuration
LayersRNConfig
interface LayersRNConfig {
appId: string;
environment: 'development' | 'staging' | 'production';
appUserId?: string;
enableDebug?: boolean; // default: false
baseUrl?: string; // default: "https://in.layers.com"
flushIntervalMs?: number; // default: 30000
flushThreshold?: number; // default: 10
maxQueueSize?: number; // default: 1000
autoTrackAppOpen?: boolean; // default: true
autoTrackDeepLinks?: boolean; // default: true
}| Option | Type | Default | Description |
| -------------------- | ------------- | ------------------------- | ------------------------------------------------ |
| appId | string | required | Your Layers application identifier. |
| environment | Environment | required | 'development', 'staging', or 'production'. |
| appUserId | string | undefined | Optional user ID to set at construction time. |
| enableDebug | boolean | false | Enable verbose console logging. |
| baseUrl | string | "https://in.layers.com" | Custom ingest API endpoint. |
| flushIntervalMs | number | 30000 | Automatic flush interval in milliseconds. |
| flushThreshold | number | 10 | Queue size that triggers an automatic flush. |
| maxQueueSize | number | 1000 | Maximum events in the queue before dropping. |
| autoTrackAppOpen | boolean | true | Automatically track app_open on init. |
| autoTrackDeepLinks | boolean | true | Automatically track deep_link_opened events. |
Core API
Constructor & Initialization
const layers = new LayersReactNative(config: LayersRNConfig);
await layers.init();The constructor creates the SDK instance with an in-memory queue. Calling init() upgrades to AsyncStorage-backed persistence, collects device info, fetches remote config, reads clipboard attribution (iOS), fires app_open, and sets up auto-tracking.
You can call track() and screen() before init() completes -- events are queued in memory.
Event Tracking
track(eventName: string, properties?: EventProperties): voidlayers.track('purchase_completed', {
product_id: 'sku_123',
price: 9.99,
currency: 'USD'
});Screen Tracking
screen(screenName: string, properties?: EventProperties): voidlayers.screen('ProductDetail', { product_id: 'sku_123' });User Identity
// Set the app user ID (set-user-once: ignored if already set)
setAppUserId(appUserId: string): void
// Clear the current user ID (allows setting a new one)
clearAppUserId(): void
// Get the current user ID
getAppUserId(): string | undefined
// Set user properties
async setUserProperties(properties: UserProperties): Promise<void>// After login
layers.setAppUserId('user_123');
await layers.setUserProperties({
email: '[email protected]',
plan: 'premium'
});
// On logout
layers.clearAppUserId();Set-user-once semantics: Once
setAppUserId()is called, subsequent calls are ignored untilclearAppUserId()is called. This prevents accidental user ID changes.
Consent Management
async setConsent(consent: ConsentState): Promise<void>
getConsentState(): ConsentStateinterface ConsentState {
analytics?: boolean;
advertising?: boolean;
}// User accepts all tracking
await layers.setConsent({ analytics: true, advertising: true });
// User denies advertising
await layers.setConsent({ analytics: true, advertising: false });
// Read current consent
const consent = layers.getConsentState();Flush & Shutdown
// Flush queued events to the server
async flush(): Promise<void>
// Shut down the SDK (removes listeners, stops timers)
shutdown(): voidSession & Device
// Get the current session ID
getSessionId(): string
// Override device context fields
setDeviceInfo(deviceInfo: DeviceContext): voidError Handling
// Register an error listener
on(event: 'error', listener: (error: Error) => void): this
// Remove an error listener
off(event: 'error', listener: (error: Error) => void): thislayers.on('error', (error) => {
console.error('Layers error:', error.message);
// Forward to your crash reporting service
});App Tracking Transparency (ATT) -- iOS
Integrated ATT (Recommended)
The requestTrackingPermission() method on the SDK instance handles the full ATT flow:
const status = await layers.requestTrackingPermission();
// Returns: 'authorized' | 'denied' | 'restricted' | 'not_determined'This method automatically:
- Shows the ATT dialog (or uses
expo-tracking-transparencyif available) - Collects IDFA if authorized
- Updates device context with IDFA and ATT status
- Sets advertising consent based on the result
Standalone ATT Functions
For more granular control, use the exported functions:
import {
getATTStatus,
getAdvertisingId,
getVendorId,
isATTAvailable,
requestTrackingAuthorization
} from '@layers/react-native';
const status = await getATTStatus();
const isAvailable = await isATTAvailable();
const idfa = await getAdvertisingId(); // null if not authorized
const idfv = await getVendorId(); // always available on iOSATTStatus
type ATTStatus = 'not_determined' | 'restricted' | 'denied' | 'authorized';Important: Add
NSUserTrackingUsageDescriptionto yourInfo.plist.
SKAdNetwork (SKAN) -- iOS
SKAN is auto-configured from the server's remote config. The SDK creates a SKANManager instance and automatically forwards every track() call through the SKAN rule engine.
Accessing the Auto-Configured Manager
const skanManager = layers.getSkanManager();
if (skanManager) {
const metrics = skanManager.getMetrics();
console.log('SKAN value:', metrics.currentValue);
console.log('SKAN preset:', metrics.currentPreset);
}Manual SKAN Configuration
If you need to configure SKAN manually instead of relying on remote config:
import { SKANManager } from '@layers/react-native';
const skan = new SKANManager((data) => {
console.log(`Conversion value updated: ${data.previousValue} -> ${data.newValue}`);
});
// Use a built-in preset
skan.setPreset('subscriptions'); // or 'engagement' or 'iap'
await skan.initialize();
// Or define custom rules
skan.setCustomRules([
{
eventName: 'purchase_success',
conditions: { revenue: { '>=': 10 } },
conversionValue: 63,
coarseValue: 'high',
priority: 10
},
{
eventName: 'trial_start',
conversionValue: 20,
priority: 5
}
]);
await skan.initialize();
// Process events manually
await skan.processEvent('purchase_success', { revenue: 49.99 });SKANConversionRule
interface SKANConversionRule {
eventName: string;
conditions?: Record<string, unknown>; // Operator-based: { '>': 10, '<': 100 }
conversionValue: number; // 0-63 (SKAN 3.0) or 0-7 (SKAN 4.0)
coarseValue?: 'low' | 'medium' | 'high'; // SKAN 4.0 only
lockWindow?: boolean; // SKAN 4.0 only
priority?: number;
description?: string;
}SKANMetrics
interface SKANMetrics {
isSupported: boolean;
version: string;
currentValue: number | null;
currentPreset: string | null;
ruleCount: number;
evaluationCount: number;
}Available Presets
subscriptions-- Optimized for subscription apps (trial start, subscription start/renew)engagement-- Optimized for engagement-driven apps (content views, sessions, bookmarks)iap-- Optimized for in-app purchase revenue tracking (purchase tiers by revenue)
Deep Links
Auto-Tracking (Default)
When autoTrackDeepLinks is true (default), the SDK automatically tracks a deep_link_opened event for every incoming deep link. The event includes the full URL, scheme, host, path, and all query parameters (UTM, click IDs) as flat top-level properties.
Manual Deep Link Handling
import { parseDeepLink, setupDeepLinkListener } from '@layers/react-native';
const unsubscribe = setupDeepLinkListener((data) => {
console.log('Deep link:', data.url);
console.log('Host:', data.host);
console.log('Path:', data.path);
console.log('UTM Source:', data.queryParams.utm_source);
console.log('Click ID:', data.queryParams.fbclid);
});
// Later: unsubscribe()The listener handles both:
- Initial URL (cold start): Checks
Linking.getInitialURL()on setup. - Subsequent URLs (warm start): Listens to
Linkingurlevents.
parseDeepLink
function parseDeepLink(url: string): DeepLinkData | null;interface DeepLinkData {
url: string;
scheme: string;
host: string;
path: string;
queryParams: Record<string, string>;
timestamp: number;
}Clipboard Attribution -- iOS
On iOS, the SDK reads the clipboard on first launch (during init()) for a Layers click URL. If found, the click URL and click ID are included as properties on the app_open event. This is controlled by the server's remote config (clipboard_attribution_enabled).
For manual reading:
import { readClipboardAttribution } from '@layers/react-native';
const data = await readClipboardAttribution();
if (data) {
console.log('Click URL:', data.clickUrl);
console.log('Click ID:', data.clickId);
}interface ClipboardAttribution {
clickUrl: string;
clickId: string;
}Requires @react-native-clipboard/clipboard as a peer dependency.
Expo Router Integration
For automatic screen tracking with Expo Router:
import { useLayersExpoRouterTracking } from '@layers/react-native';
import { usePathname, useGlobalSearchParams } from 'expo-router';
function RootLayout() {
useLayersExpoRouterTracking(layers, usePathname, useGlobalSearchParams);
return <Stack />;
}This hook automatically tracks a screen event every time the Expo Router pathname changes, including route parameters as event properties.
Install ID
The SDK generates and persists a unique install ID via AsyncStorage:
import { getOrSetInstallId } from '@layers/react-native';
const installId = await getOrSetInstallId();This ID persists across app sessions and is included in the device context.
Automatic Behaviors
- app_open event: Tracked on
init()with clipboard attribution (iOS). - deep_link_opened event: Tracked automatically for all incoming deep links.
- Background flush: Events are flushed when the app goes to background/inactive.
- Foreground flush: Events are flushed when the app becomes active.
- Network reconnect flush: Events are flushed when the device reconnects (requires
@react-native-community/netinfo). - Periodic flush: Events are flushed on a timer (configurable).
- Remote config: Server configuration is fetched during init.
- SKAN auto-config: SKAN preset/rules from remote config are applied automatically (iOS).
- Device context: Platform, OS version, device model, locale, screen size, timezone, IDFV (iOS), and install ID are collected automatically.
- Event persistence: Events are persisted via AsyncStorage (if available) and rehydrated on restart.
TypeScript Types
All types are exported from the package:
import type {
ATTStatus,
ClipboardAttribution,
ConsentState,
DeepLinkData,
DeviceContext,
Environment,
ErrorListener,
EventProperties,
LayersRNConfig,
SKANConversionRule,
SKANMetrics,
SKANPresetConfig,
UserProperties
} from '@layers/react-native';