@metarouter/react-native-sdk
v1.5.0
Published
[](https://www.npmjs.com/package/@metarouter/react-native-sdk) [](./LICENSE)
Downloads
154
Readme
@metarouter/react-native-sdk
A lightweight React Native analytics SDK that transmits events to your MetaRouter cluster.
Table of Contents
- Installation
- Usage
- API Reference
- Features
- Compatibility
- Debugging
- Identity Persistence
- Advertising ID (IDFA/GAID)
- Using the alias() Method
- License
Installation
npm install @metarouter/react-native-sdk @react-native-async-storage/async-storage react-native-device-infoUsage
Basic Setup
import { createAnalyticsClient } from "@metarouter/react-native-sdk";
// Initialize the analytics client
const analytics = await createAnalyticsClient({
writeKey: "your-write-key",
ingestionHost: "https://your-ingestion-endpoint.com",
debug: true, // Optional: enable debug mode
flushIntervalSeconds: 30, // Optional: flush events every 30 seconds
});React Context Usage
import React, { useEffect, useState } from "react";
import {
createAnalyticsClient,
MetaRouterProvider,
useMetaRouter,
} from "@metarouter/react-native-sdk";
const App = () => {
const [analyticsClient, setAnalyticsClient] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const initializeAnalytics = async () => {
try {
// Initialize the analytics client
const client = await createAnalyticsClient({
writeKey: "your-write-key",
ingestionHost: "https://your-ingestion-endpoint.com",
});
setAnalyticsClient(client);
} catch (error) {
console.error("Failed to initialize analytics:", error);
} finally {
setIsLoading(false);
}
};
initializeAnalytics();
}, []);
if (isLoading) return null; // or a loading spinner
return (
<MetaRouterProvider analyticsClient={analyticsClient}>
<YourApp />
</MetaRouterProvider>
);
};
// Use analytics in any component
const MyComponent = () => {
const { analytics } = useMetaRouter();
const handleButtonPress = () => {
analytics.track("Button Pressed", {
buttonName: "submit",
timestamp: Date.now(),
});
};
return <Button onPress={handleButtonPress} title="Submit" />;
};Direct Usage
import { createAnalyticsClient } from "@metarouter/react-native-sdk";
// Initialize the client (optionally await it), but you can use it at any time
// with events transmitted when the client is ready.
const analytics = await createAnalyticsClient({
writeKey: "your-write-key",
ingestionHost: "https://your-ingestion-endpoint.com",
});
// Track events
analytics.track("User Action", {
action: "button_click",
screen: "home",
});
// Identify users
analytics.identify("user123", {
name: "John Doe",
email: "[email protected]",
});
// Track screen views
analytics.screen("Home Screen", {
category: "navigation",
});
// Track page views
analytics.page("Home Page", {
url: "/home",
referrer: "/landing",
});
// Group users
analytics.group("company123", {
name: "Acme Corp",
industry: "technology",
});
// Flush events immediately
analytics.flush();
// Reset analytics (useful for testing or logout)
await analytics.reset();API Reference
createAnalyticsClient(options)
Initializes the analytics client and returns a live proxy to the client instance.
⚠️ createAnalyticsClient() is asynchronous, but you do not need to await it before using analytics methods.
Calls to track, identify, etc. are buffered in-memory by the proxy and replayed in order once the client is fully initialized.
Options:
writeKey(string, required): Your write keyingestionHost(string, required): Your MetaRouter ingestor hostdebug(boolean, optional): Enable debug modeflushIntervalSeconds(number, optional): Interval in seconds to flush eventsmaxQueueEvents(number, optional): number of max events stored in memory
Proxy behavior (quick notes):
- Buffer is in-memory only (not persisted). Calls made before ready are lost if the process exits.
- Ordering is preserved relative to other buffered calls; normal FIFO + batching applies after ready.
- On fatal config errors (
401/403/404), the client enters disabled state and drops subsequent calls. sentAtis stamped when the batch is prepared for transmission (just before network send). If you need the original occurrence time, pass your owntimestampon each event.
Analytics Interface
The analytics client provides the following methods:
track(event: string, properties?: Record<string, any>): Track custom eventsidentify(userId: string, traits?: Record<string, any>): Identify usersgroup(groupId: string, traits?: Record<string, any>): Group usersscreen(name: string, properties?: Record<string, any>): Track screen viewspage(name: string, properties?: Record<string, any>): Track page viewsalias(newUserId: string): Connect anonymous users to known user IDs. See Using the alias() Method for detailssetAdvertisingId(advertisingId: string): Set the advertising identifier (IDFA on iOS, GAID on Android) for ad tracking. See Advertising ID section for usage and compliance requirementsclearAdvertisingId(): Clear the advertising identifier from storage and context. Useful for GDPR/CCPA compliance when users opt out of ad trackingsetTracing(enabled: boolean): Enable or disable tracing headers on API requests. When enabled, includes aTrace: trueheader for debugging request flowsflush(): Flush events immediatelyreset(): Reset analytics state and clear all stored data (includes clearing advertising ID)enableDebugLogging(): Enable debug logginggetDebugInfo(): Get current debug information
React Hooks
useMetaRouter(): Hook to access the analytics client within aMetaRouterProvider
Components
MetaRouterProvider: React context provider for the analytics client
Features
- 🎯 Custom Endpoints: Send events to your own ingestion endpoints
- 📱 React Native Optimized: Built specifically for React Native
- 🎣 React Hooks: Easy integration with React components
- 🔧 TypeScript Support: Full TypeScript support included
- 🚀 Lightweight: Minimal overhead and dependencies
- 🔄 Reset Capability: Easily reset analytics state for testing or logout scenarios
- 🐛 Debug Support: Built-in debugging tools for troubleshooting
✅ Compatibility
| Component | Supported Versions | | --------------------- | ------------------ | | React Native | >= 0.63 | | React | >= 16.8 | | iOS Deployment Target | >= iOS 10 | | Android Min SDK | >= API 16 | | Node.js | >= 16 |
Debugging
If you're not seeing API calls being made, here are some steps to troubleshoot:
1. Enable Debug Logging
// Initialize with debug enabled
const analytics = await createAnalyticsClient({
writeKey: "your-write-key",
ingestionHost: "https://your-ingestion-endpoint.com",
debug: true, // This enables detailed logging
});
// Or enable debug logging after initialization
analytics.enableDebugLogging?.();2. Check Debug Information
// Get current state information
const debugInfo = await analytics.getDebugInfo?.();
console.log("Analytics debug info:", debugInfo);3. Force Flush Events
// Manually flush events to see if they're being sent
await analytics.flush();4. Common Issues
- Network Permissions: Ensure your app has network permissions
- AsyncStorage: The SDK uses AsyncStorage for identity persistence (anonymousId, userId, groupId, advertisingId)
- Endpoint URL: Verify your ingestion endpoint is correct and accessible
- Write Key: Ensure your write key is valid
Delivery & Backoff (How events flow under failures)
Queue capacity: The SDK keeps up to 2,000 events in memory. When the cap is reached, the oldest events are dropped first (drop-oldest). You can change this via maxQueueEvents in createAnalyticsClient(options)
This SDK uses a circuit breaker around network I/O. It keeps ordering stable, avoids tight retry loops, and backs off cleanly when your cluster is unhealthy or throttling.
Queueing during backoff: While the breaker is OPEN, new events are accepted and appended to the in-memory queue; nothing is sent until the cooldown elapses.
Ordering (FIFO): If a batch fails with a retryable error, that batch is requeued at the front (original order preserved). New events go to the tail. After cooldown, we try again; on success we continue draining in order.
Half-open probe: After cooldown, one probe is allowed. Success → breaker CLOSED (keep flushing). Failure → breaker OPEN again with longer cooldown.
sentAt semantics: sentAt is stamped when the event is enqueued. If the client is backing off, the actual transmit may be later; sentAt reflects when the event entered the queue.
| Status / Failure | Action | Breaker | Queue effect |
| ----------------------------------- | -------------------------------------------------------------------- | ------- | ------------------------------ |
| 2xx | Success | close | Batch removed |
| 5xx | Retry: requeue front, schedule after cooldown | open↑ | Requeued (front) |
| 408 (timeout) | Retry: requeue front, schedule after cooldown | open↑ | Requeued (front) |
| 429 (throttle) | Retry: requeue front, wait = max(Retry-After, breaker, 1000ms) | open↑ | Requeued (front) |
| 413 (payload too large) | Halve maxBatchSize; requeue and retry; if already 1, drop | close | Requeued or dropped (size=1) |
| 400, 422, other non-fatal 4xx | Drop bad batch, continue | close | Dropped |
| 401, 403, 404 | Disable client (stop timers), clear queue | close | Cleared |
| Network error / Abort / Timeout | Retry: requeue front, schedule after cooldown | open↑ | Requeued (front) |
| Reset during flush | Do not requeue in-flight chunk; drop it | — | Dropped |
Defaults: failureThreshold=3, cooldownMs=10s, maxCooldownMs=120s, jitter=±20%, halfOpenMaxConcurrent=1.
Identifiers:
anonymousIdis a stable, persisted UUID for the device/user before identify; it does not include timestamps.messageIdis generated as<epochMillis>-<uuid>(e.g.,1734691572843-6f0c7e85-...) to aid debugging.
Identity Persistence
The MetaRouter React Native SDK automatically manages and persists user identifiers across app sessions using React Native's AsyncStorage. This ensures consistent user tracking even after app restarts.
The Four Identity Fields
1. userId (Common User ID)
The userId is set when you identify a user and represents their unique identifier in your system (e.g., database ID, email, employee ID).
How to set:
analytics.identify("user123", {
name: "John Doe",
email: "[email protected]",
role: "Sales Associate",
});Behavior:
- Persisted to device storage (
AsyncStoragekey:metarouter:user_id) - Automatically loaded on app restart
- Automatically included in all subsequent events (
track,page,screen,group) - Remains set until
reset()is called or app is uninstalled
Example flow:
// Day 1: User logs in
analytics.identify("employeeID", { name: "Jane" });
analytics.track("Product Viewed", { sku: "ABC123" });
// Event includes: userId: "employeeID"
// App restarts...
// Day 2: User opens app
analytics.track("App Opened");
// Event STILL includes: userId: "employeeID" (auto-loaded from storage)2. anonymousId
The anonymousId is a unique identifier automatically generated for each device/installation before a user is identified.
How it's set:
- Automatically generated as a UUID v4 on first SDK initialization
- No manual action required
Behavior:
- Persisted to device storage (
AsyncStoragekey:metarouter:anonymous_id) - Automatically loaded on app restart
- Automatically included in all events
- Remains stable across app sessions until
reset()is called - Cleared on
reset()and a new UUID is generated on nextinit()
Use case:
Track user behavior before they log in or create an account, then connect pre-login and post-login activity using the alias() method.
3. groupId
The groupId associates a user with an organization, team, account, or other group entity.
How to set:
analytics.group("company123", {
name: "Acme Corp",
plan: "Enterprise",
industry: "Technology",
});Behavior:
- Persisted to device storage (
AsyncStoragekey:metarouter:group_id) - Automatically loaded on app restart
- Automatically included in all subsequent events after being set
- Remains set until
reset()is called
Example use case:
// User logs into their company account
analytics.identify("user123", { name: "Jane" });
analytics.group("acme-corp", { name: "Acme Corp" });
// All future events include both userId and groupId
analytics.track("Report Generated");
// Event includes: userId: "user123", groupId: "acme-corp"4. advertisingId (Optional)
The advertisingId is used for ad tracking and attribution (IDFA on iOS, GAID on Android). See the Advertising ID section below for detailed usage and compliance requirements.
Persistence Summary
| Field | Set By | Storage Key | Auto-Attached | Cleared By |
| ----------------- | ------------------------ | --------------------------- | -------------------- | ----------------------------------------- |
| userId | identify(userId) | metarouter:user_id | All events | reset() |
| anonymousId | Auto-generated (UUID v4) | metarouter:anonymous_id | All events | reset() (new ID generated on next init) |
| groupId | group(groupId) | metarouter:group_id | All events after set | reset() |
| advertisingId | setAdvertisingId(id) | metarouter:advertising_id | Event context | clearAdvertisingId(), reset() |
Event Enrichment Flow
Every event you send (track, page, screen, group) is automatically enriched with persisted identity information:
// You call:
analytics.track("Button Clicked", { buttonName: "Submit" });
// SDK automatically adds:
{
"type": "track",
"event": "Button Clicked",
"properties": { "buttonName": "Submit" },
"userId": "employeeID", // ← Auto-added from storage
"anonymousId": "a1b2c3d4-...", // ← Auto-added from storage
"groupId": "company123", // ← Auto-added from storage (if set)
"timestamp": "2025-10-23T...",
"context": {
"device": {
"advertisingId": "..." // ← Auto-added from storage (if set)
}
}
}Resetting Identity
Call reset() to clear all identity data, typically when a user logs out:
await analytics.reset();What reset() does:
- Clears
userId,anonymousId,groupId, andadvertisingIdfrom memory - Removes all identity fields from AsyncStorage
- Stops background flush loops
- Clears event queue
- Next
init()will generate a newanonymousId
Common logout flow:
// User logs out
await analytics.reset();
// User is now tracked with a new anonymousId (auto-generated on next event)
// No userId or groupId until they log in againBest Practices
- On Login: Call
identify()immediately after successful authentication - On Logout: Call
reset()to clear user identity - Cross-Session Tracking: The SDK handles this automatically - no action needed
- Group Associations: Set
groupIdafter determining the user's organization/team - Pre-Login Tracking: Events are tracked with
anonymousIdbefore login - Connecting Sessions: Use
alias()to connect pre-login and post-login activity
Example: Complete User Journey
// App starts - SDK initializes
const analytics = await createAnalyticsClient({...});
// anonymousId: "abc-123" (auto-generated and persisted)
// User browses before login
analytics.track("Product Viewed", { sku: "XYZ" });
// Includes: anonymousId: "abc-123"
// User logs in
analytics.identify("user456", { name: "John", email: "[email protected]" });
// userId: "user456" is now persisted
// User performs actions
analytics.track("Added to Cart", { sku: "XYZ" });
// Includes: userId: "user456", anonymousId: "abc-123"
// App closes and reopens...
// SDK auto-loads userId from storage
analytics.track("App Reopened");
// STILL includes: userId: "user456", anonymousId: "abc-123"
// User logs out
await analytics.reset();
// All IDs cleared, new anonymousId will be generated on next initStorage Location
All identity data is stored in React Native AsyncStorage, which provides:
- Persistent storage across app sessions
- Automatic data encryption on iOS (Keychain-backed)
- Secure local storage on Android
- Cleared only on app uninstall or explicit
reset()call
Using the alias() Method
The alias() method connects an anonymous user (tracked by anonymousId) to a known user ID. It's used to link pre-login activity to post-login identity.
When to Use alias()
Use alias() when a user signs up or logs in for the first time, and you want to connect their pre-login browsing activity to their new account.
Primary use case: Connecting anonymous browsing sessions to newly created user accounts.
How It Works
analytics.alias(newUserId);This does two things:
- Sets the new
userId(same asidentify()) - Sends an
aliasevent to your analytics backend, telling it: "This anonymousId and this userId are the same person"
Example: User Sign-Up Flow
// App starts - user is anonymous
const analytics = await createAnalyticsClient({...});
// anonymousId: "abc-123" (auto-generated)
// User browses anonymously
analytics.track("Product Viewed", { productId: "XYZ" });
analytics.track("Add to Cart", { productId: "XYZ" });
// Both events tracked with anonymousId: "abc-123"
// User creates an account / signs up
analytics.alias("user-456");
// Sends alias event connecting: anonymousId "abc-123" → userId "user-456"
// Optionally add user traits
analytics.identify("user-456", {
name: "John Doe",
email: "[email protected]"
});
// Future events now tracked as authenticated user
analytics.track("Purchase Complete", { orderId: "789" });
// Event includes: userId: "user-456", anonymousId: "abc-123"alias() vs identify()
| Method | When to Use | What It Does |
| ---------------- | --------------------------------------------------------------- | -------------------------------------------------------------- |
| alias() | First-time sign-up/login when connecting anonymous activity | Sets userId + sends alias event to link anonymousId → userId |
| identify() | Subsequent logins or updating user traits | Sets userId + sends identify event with user traits |
Best Practices
- First-time sign-up: Call
alias()to connect anonymous activity to the new account - Subsequent logins: Use
identify()- no need to alias again - Backend support: Ensure your analytics backend supports alias events for merging user profiles
- One-time operation: You typically only need
alias()once per user - when they first create an account
Real-World Example: E-Commerce App
// Day 1: Anonymous browsing
analytics.track("App Opened");
analytics.track("Product Viewed", { sku: "SHOE-123" });
analytics.track("Product Viewed", { sku: "SHIRT-456" });
// All tracked with anonymousId: "anon-xyz"
// User signs up
analytics.alias("user-789");
analytics.identify("user-789", {
name: "Jane Doe",
email: "[email protected]",
});
// User continues shopping (now authenticated)
analytics.track("Added to Cart", { sku: "SHIRT-456" });
analytics.track("Purchase", { total: 49.99 });
// Your analytics platform can now show the complete customer journey:
// - Pre-signup activity (anonymous product views)
// - Post-signup activity (cart additions, purchase)
// - Full conversion funnel from anonymous → identified → convertedAdvertising ID (IDFA/GAID)
The SDK supports including advertising identifiers (IDFA on iOS, GAID on Android) in event context for ad tracking and attribution purposes.
Usage
Use the setAdvertisingId() method to set the advertising identifier after initializing the analytics client:
const analytics = await createAnalyticsClient({
writeKey: "your-write-key",
ingestionHost: "https://your-ingestion-endpoint.com",
});
// Set advertising ID after initialization
await analytics.setAdvertisingId("your-advertising-id"); // IDFA on iOS, GAID on AndroidOnce set, the advertisingId will be automatically included in the device context of all subsequent events:
{
"context": {
"device": {
"advertisingId": "your-advertising-id",
"manufacturer": "Apple",
"model": "iPhone 14",
...
}
}
}Privacy & Compliance
⚠️ Important: Advertising identifiers are Personally Identifiable Information (PII). Before collecting advertising IDs, you must:
- Obtain User Consent: Request explicit permission from users before tracking
- Comply with Regulations: Follow GDPR, CCPA, and other applicable privacy laws
- App Store Requirements:
- iOS: Follow Apple's App Tracking Transparency (ATT) framework
- Android: Follow Google Play's advertising ID policies
iOS Example (with ATT)
Note: The examples below use third-party libraries for demonstration purposes. You should choose appropriate packages that fit your project's needs and are actively maintained.
import { AppTrackingTransparency } from "react-native-tracking-transparency";
import { getAdvertisingId } from "@react-native-community/google-advertiser-id"; // or similar library
// Initialize analytics first
const analytics = await createAnalyticsClient({
writeKey: "your-write-key",
ingestionHost: "https://your-ingestion-endpoint.com",
});
// Request tracking permission
const trackingStatus =
await AppTrackingTransparency.requestTrackingAuthorization();
if (trackingStatus === "authorized") {
// Get and set IDFA only if authorized
const advertisingId = await getAdvertisingId();
await analytics.setAdvertisingId(advertisingId);
}Android Example
import { getAdvertisingId } from "@react-native-community/google-advertiser-id"; // or similar library
// Initialize analytics first
const analytics = await createAnalyticsClient({
writeKey: "your-write-key",
ingestionHost: "https://your-ingestion-endpoint.com",
});
// Check if user has opted out of personalized ads
const { advertisingId, isLimitAdTrackingEnabled } = await getAdvertisingId();
if (!isLimitAdTrackingEnabled && advertisingId) {
await analytics.setAdvertisingId(advertisingId);
}Clearing Advertising ID
When users opt out of ad tracking or revoke consent, use clearAdvertisingId() to remove the advertising ID from storage and context:
// User opts out of ad tracking
await analytics.clearAdvertisingId();
// All subsequent events will not include advertisingId in context
analytics.track("Event After Opt Out");Note: The reset() method also clears the advertising ID along with all other analytics data.
Validation
The SDK validates advertising IDs before setting them:
- Must be a non-empty string
- Cannot be only whitespace
- Invalid values are rejected and logged as warnings
License
MIT
Attributions
This library includes code from the following third-party packages:
- react-native-uuid, MIT License
