@repliqo/sdk-react-native 0.3.8 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/INTEGRATION_GUIDE.md +1384 -1312
  2. package/android/src/main/java/com/repliqo/screencapture/MultiWindowCapture.java +242 -242
  3. package/android/src/main/java/com/repliqo/screencapture/ScreenCaptureModule.java +94 -94
  4. package/dist/components/RepliqoFlatList.d.ts +5 -0
  5. package/dist/components/RepliqoFlatList.js +39 -0
  6. package/dist/components/RepliqoScrollView.d.ts +3 -0
  7. package/dist/components/RepliqoScrollView.js +16 -265
  8. package/dist/components/useRepliqoScrollTracking.d.ts +33 -0
  9. package/dist/components/useRepliqoScrollTracking.js +304 -0
  10. package/dist/core/client.d.ts +52 -0
  11. package/dist/core/client.js +304 -16
  12. package/dist/core/config.d.ts +2 -2
  13. package/dist/core/config.js +8 -2
  14. package/dist/core/storage.d.ts +29 -0
  15. package/dist/core/storage.js +33 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.js +3 -1
  18. package/dist/snapshot/capture.d.ts +7 -0
  19. package/dist/snapshot/capture.js +17 -5
  20. package/dist/trackers/error.tracker.d.ts +25 -0
  21. package/dist/trackers/error.tracker.js +114 -37
  22. package/dist/trackers/navigation.tracker.js +11 -0
  23. package/dist/transport/api.client.d.ts +34 -1
  24. package/dist/transport/api.client.js +102 -19
  25. package/dist/transport/batch-queue.d.ts +53 -1
  26. package/dist/transport/batch-queue.js +126 -19
  27. package/dist/types/events.d.ts +12 -0
  28. package/package.json +68 -64
  29. package/src/components/RepliqoFlatList.tsx +64 -0
  30. package/src/components/RepliqoScrollView.tsx +53 -302
  31. package/src/components/useRepliqoScrollTracking.ts +366 -0
  32. package/src/core/client.ts +953 -628
  33. package/src/core/config.ts +40 -30
  34. package/src/core/storage.ts +51 -0
  35. package/src/index.ts +54 -50
  36. package/src/snapshot/NativeScreenCapture.ts +62 -62
  37. package/src/snapshot/capture.ts +154 -142
  38. package/src/trackers/error.tracker.ts +211 -136
  39. package/src/trackers/navigation.tracker.ts +107 -98
  40. package/src/transport/api.client.ts +430 -327
  41. package/src/transport/batch-queue.ts +212 -95
  42. package/src/types/events.ts +72 -60
@@ -1,1312 +1,1384 @@
1
- # Repliqo SDK for React Native - Integration Guide
2
-
3
- > **Version:** 0.1.0
4
- > **Package:** `@repliqo/sdk-react-native`
5
- > **Last updated:** March 2026
6
-
7
- ---
8
-
9
- ## Table of Contents
10
-
11
- 1. [Overview](#1-overview)
12
- 2. [Prerequisites](#2-prerequisites)
13
- 3. [Installation](#3-installation)
14
- 4. [Quick Start (5-Minute Setup)](#4-quick-start-5-minute-setup)
15
- 5. [Configuration Reference](#5-configuration-reference)
16
- 6. [Navigation Tracking (Automatic)](#6-navigation-tracking-automatic)
17
- 7. [Custom Events](#7-custom-events)
18
- 8. [Error/Crash Tracking](#8-errorcrash-tracking)
19
- 9. [Manual Screen Tracking](#9-manual-screen-tracking)
20
- 10. [Session Management](#10-session-management)
21
- 11. [Device Info](#11-device-info)
22
- 12. [Advanced: Flush Control](#12-advanced-flush-control)
23
- 13. [Advanced: Debug Mode](#13-advanced-debug-mode)
24
- 14. [Dashboard Guide](#14-dashboard-guide)
25
- 15. [API Reference](#15-api-reference)
26
- 16. [Screen Capture Architecture](#16-screen-capture-architecture)
27
- 17. [Troubleshooting](#17-troubleshooting)
28
- 17. [TypeScript Types](#17-typescript-types)
29
-
30
- ---
31
-
32
- ## 1. Overview
33
-
34
- Repliqo is a lightweight analytics SDK designed specifically for React Native applications. It provides developers with deep visibility into how users interact with their mobile apps by capturing navigation patterns, screen visits, touch events, crashes, and custom events -- all with minimal performance overhead and zero impact on your app's startup time.
35
-
36
- The SDK works on a buffer-and-batch architecture: events are collected in memory as users interact with your app, then sent to the Repliqo backend in efficient batches. The backend stores and processes this data, which you can then explore through the Repliqo dashboard. The dashboard provides session replays, heatmaps, funnel analysis, crash reports, and data export capabilities. Because events are batched rather than sent one-by-one, the SDK keeps network usage low and never blocks the main thread.
37
-
38
- ---
39
-
40
- ## 2. Prerequisites
41
-
42
- Before integrating the Repliqo SDK, make sure your project meets the following requirements:
43
-
44
- - **React Native** >= 0.68.0
45
- - **React** >= 17.0.0
46
- - **@react-navigation/native** >= 6.0.0 (required only if you want automatic navigation tracking)
47
- - **A Repliqo account with an API key**
48
-
49
- ### Getting Your API Key
50
-
51
- 1. Go to the Repliqo dashboard at **[https://repliqo.odmservice.site](https://repliqo.odmservice.site)**.
52
- 2. Sign in or create a new account.
53
- 3. Navigate to **Settings > Applications** and click **Create Application**.
54
- 4. Give your app a name (this becomes your `appId`) and note the **API key** that is generated.
55
- 5. Copy both the `appId` and the `apiKey` -- you will need them during SDK initialization.
56
-
57
- ---
58
-
59
- ## 3. Installation
60
-
61
- ### From a Local Path (Development)
62
-
63
- If you have the SDK source code locally (e.g., in a monorepo or cloned repository), install it by referencing the local path:
64
-
65
- ```bash
66
- npm install file:../path/to/sdk-react-native
67
- ```
68
-
69
- For example, if your project structure looks like this:
70
-
71
- ```
72
- my-workspace/
73
- app-analytics-api/
74
- packages/
75
- sdk-react-native/ # <-- The SDK
76
- my-react-native-app/ # <-- Your app
77
- ```
78
-
79
- Run this from your React Native app directory:
80
-
81
- ```bash
82
- npm install file:../app-analytics-api/packages/sdk-react-native
83
- ```
84
-
85
- ### From npm (Future)
86
-
87
- Once the SDK is published to npm, you will be able to install it with:
88
-
89
- ```bash
90
- npm install @repliqo/sdk-react-native
91
- ```
92
-
93
- ### Post-Installation
94
-
95
- After installing, if Metro bundler has trouble resolving the module, clear the cache:
96
-
97
- ```bash
98
- npx react-native start --reset-cache
99
- ```
100
-
101
- The SDK includes a native Android module for **multi-window screen capture** (captures modals, alerts, and dialogs). It autolinks automatically via React Native CLI — no manual linking, no `settings.gradle` edits, no `MainApplication` changes needed.
102
-
103
- On Android, the native module uses `View.draw()` on all app windows to composite a full screenshot including overlays. **No permissions are required** — it does not use MediaProjection or screen recording APIs.
104
-
105
- On iOS, the native module is not yet implemented. The SDK falls back to `react-native-view-shot` which captures the main view only.
106
-
107
- > **Note:** After installing, you must rebuild the native app (`npx react-native run-android`) for the native module to be linked. A Metro-only restart is not sufficient.
108
-
109
- ---
110
-
111
- ## 4. Quick Start (5-Minute Setup)
112
-
113
- This section gets you up and running with the absolute minimum configuration. After completing these two steps, navigation tracking and screen visit analytics will work automatically.
114
-
115
- ### Step 1: Create the Analytics Service File
116
-
117
- Create a file at `src/services/repliqo.ts` in your project:
118
-
119
- ```typescript
120
- // src/services/repliqo.ts
121
-
122
- /**
123
- * Repliqo Analytics SDK - Initialization and session management.
124
- *
125
- * This file initializes the SDK and starts a session with device info.
126
- * Import and call initRepliqo() from your App.tsx on startup.
127
- */
128
- import { AppAnalytics } from '@repliqo/sdk-react-native';
129
- import { Platform, Dimensions } from 'react-native';
130
-
131
- // Replace these with your actual values from the Repliqo dashboard
132
- const REPLIQO_API_KEY = 'your-api-key-here';
133
- const REPLIQO_APP_ID = 'your-app-id';
134
-
135
- // Use localhost for development, production URL for release builds
136
- const REPLIQO_API_URL = __DEV__
137
- ? 'http://10.0.2.2:3050' // Android emulator localhost
138
- : 'https://api-repliqo.odmservice.site';
139
-
140
- // Guard against double-initialization
141
- let initialized = false;
142
-
143
- export async function initRepliqo(): Promise<void> {
144
- if (initialized) return;
145
-
146
- try {
147
- // 1. Initialize the SDK with your configuration
148
- AppAnalytics.init({
149
- apiKey: REPLIQO_API_KEY,
150
- appId: REPLIQO_APP_ID,
151
- baseUrl: REPLIQO_API_URL,
152
- enableTouchTracking: false, // Disable if you don't need heatmaps
153
- enableNavigationTracking: true, // Automatic navigation events
154
- enableCrashTracking: true, // Capture JS errors and unhandled rejections
155
- enableSnapshots: false, // Disable for lower overhead
156
- batchSize: 20, // Send events in groups of 20
157
- flushInterval: 30000, // Flush every 30 seconds
158
- debug: __DEV__, // Log SDK activity in development
159
- });
160
-
161
- // 2. Gather device information
162
- const { width, height } = Dimensions.get('window');
163
- const deviceId = `${Platform.OS}-${Math.random().toString(36).slice(2, 10)}`;
164
-
165
- // 3. Start a session -- this registers the device with the backend
166
- await AppAnalytics.getInstance().startSession(deviceId, {
167
- os: Platform.OS,
168
- osVersion: String(Platform.Version),
169
- model: 'Unknown', // Use react-native-device-info for real values
170
- brand: 'Unknown',
171
- screenWidth: width,
172
- screenHeight: height,
173
- appVersion: '1.0.0', // Replace with your app version
174
- });
175
-
176
- initialized = true;
177
- if (__DEV__) console.log('[Repliqo] Session started');
178
- } catch (error) {
179
- // Never let analytics break the host app
180
- if (__DEV__) console.warn('[Repliqo] Init failed:', error);
181
- }
182
- }
183
- ```
184
-
185
- ### Step 2: Call It from App.tsx
186
-
187
- In your root `App.tsx`, import and call `initRepliqo()` inside a `useEffect`. Use a `setTimeout` to ensure analytics initialization never blocks your app's startup:
188
-
189
- ```typescript
190
- // App.tsx
191
-
192
- import React, { useEffect } from 'react';
193
- import { SafeAreaProvider } from 'react-native-safe-area-context';
194
- import { AppNavigator } from './src/navigation/AppNavigator';
195
- import { initRepliqo } from './src/services/repliqo';
196
-
197
- function App() {
198
- useEffect(() => {
199
- // Initialize analytics after a short delay so it never blocks app startup
200
- setTimeout(initRepliqo, 3000);
201
- }, []);
202
-
203
- return (
204
- <SafeAreaProvider>
205
- <AppNavigator />
206
- </SafeAreaProvider>
207
- );
208
- }
209
-
210
- export default App;
211
- ```
212
-
213
- That is it. With these two files in place, the SDK will:
214
-
215
- - Start a session with device metadata.
216
- - Automatically flush events when the app goes to the background.
217
- - Track navigation events and screen visits once you wire up the navigation tracker (see [Section 6](#6-navigation-tracking-automatic)).
218
-
219
- ---
220
-
221
- ## 5. Configuration Reference
222
-
223
- All configuration is passed to `AppAnalytics.init()` via an `SDKConfig` object. The three required fields have no defaults and must always be provided. All other fields are optional and have sensible defaults.
224
-
225
- | Property | Type | Default | Required | Description |
226
- |---|---|---|---|---|
227
- | `apiKey` | `string` | -- | Yes | Your Repliqo API key. Obtained from the dashboard under Settings > Applications. |
228
- | `appId` | `string` | -- | Yes | Your application identifier. Must match the app ID registered in the dashboard. |
229
- | `baseUrl` | `string` | -- | Yes | The Repliqo API base URL. Use `https://api-repliqo.odmservice.site` for production, or a local URL like `http://10.0.2.2:3050` for Android emulator development. |
230
- | `batchSize` | `number` | `20` | No | Number of events to accumulate before sending a batch to the server. Lower values mean more frequent network requests; higher values reduce request count but increase the risk of data loss if the app is killed. |
231
- | `flushInterval` | `number` | `30000` | No | Time in milliseconds between automatic batch flushes. Events are sent at least this often, even if `batchSize` has not been reached. Default is 30 seconds. |
232
- | `enableTouchTracking` | `boolean` | `true` | No | When `true`, touch events (taps) are captured and sent to the backend for heatmap generation. Set to `false` if you only need navigation analytics. |
233
- | `enableNavigationTracking` | `boolean` | `true` | No | When `true`, navigation events (screen transitions) are tracked. Works with `useNavigationTracker()` or `trackScreenChange()`. |
234
- | `enableSnapshots` | `boolean` | `true` | No | When `true`, the SDK periodically captures a serialized snapshot of the component tree. Useful for session replay. Increases memory and bandwidth usage. |
235
- | `enableCrashTracking` | `boolean` | `true` | No | When `true`, the SDK installs a global error handler and captures unhandled JavaScript errors and Promise rejections. Crash reports are sent immediately, not batched. |
236
- | `snapshotInterval` | `number` | `1000` | No | Time in milliseconds between automatic snapshot captures. Only relevant when `enableSnapshots` is `true`. Default is 1 second. |
237
- | `maxSnapshotsPerSession` | `number` | `3600` | No | Maximum number of snapshots the SDK will capture per session. Prevents runaway memory usage in long-lived sessions. At the default of 1 snapshot/second, this allows 1 hour of capture. |
238
- | `debug` | `boolean` | `false` | No | When `true`, the SDK logs detailed information to the console (initialization, event tracking, flushes, errors). Recommended to set `debug: __DEV__` so logs only appear during development. |
239
-
240
- ---
241
-
242
- ## 6. Navigation Tracking (Automatic)
243
-
244
- The SDK provides two approaches for automatic navigation tracking with `@react-navigation/native`. Choose the one that best fits your app's architecture.
245
-
246
- ### Approach A: Using the `useNavigationTracker()` Hook
247
-
248
- The `useNavigationTracker()` hook is the cleanest integration method. It returns a `navigationRef`, an `onReady` callback, and an `onStateChange` callback that you pass directly to `NavigationContainer`.
249
-
250
- ```typescript
251
- // src/navigation/AppNavigator.tsx
252
-
253
- import React from 'react';
254
- import { NavigationContainer } from '@react-navigation/native';
255
- import { createStackNavigator } from '@react-navigation/stack';
256
- import { useNavigationTracker } from '@repliqo/sdk-react-native';
257
- import { HomeScreen } from '../screens/HomeScreen';
258
- import { ProfileScreen } from '../screens/ProfileScreen';
259
- import { SettingsScreen } from '../screens/SettingsScreen';
260
-
261
- const Stack = createStackNavigator();
262
-
263
- export function AppNavigator() {
264
- // The hook manages all navigation tracking state internally
265
- const { navigationRef, onReady, onStateChange } = useNavigationTracker();
266
-
267
- return (
268
- <NavigationContainer
269
- ref={navigationRef}
270
- onReady={onReady}
271
- onStateChange={onStateChange}
272
- >
273
- <Stack.Navigator screenOptions={{ headerShown: false }}>
274
- <Stack.Screen name="Home" component={HomeScreen} />
275
- <Stack.Screen name="Profile" component={ProfileScreen} />
276
- <Stack.Screen name="Settings" component={SettingsScreen} />
277
- </Stack.Navigator>
278
- </NavigationContainer>
279
- );
280
- }
281
- ```
282
-
283
- When the navigator is ready, the hook records the initial screen. On every subsequent state change, it automatically:
284
-
285
- 1. Detects the currently active route (including nested navigators).
286
- 2. Calls `trackNavigation(fromScreen, toScreen)` to log the transition event.
287
- 3. Calls `onScreenExit(fromScreen)` to record how long the user spent on the previous screen.
288
- 4. Calls `onScreenEnter(toScreen)` to start the timer for the new screen.
289
-
290
- ### Approach B: Using `trackScreenChange()` Manually
291
-
292
- If you need more control over how navigation state is read (for example, with complex nested navigators or non-standard setups), use the `trackScreenChange()` function directly:
293
-
294
- ```typescript
295
- // src/navigation/AppNavigator.tsx
296
-
297
- import React, { useRef, useCallback } from 'react';
298
- import { NavigationContainer } from '@react-navigation/native';
299
- import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
300
- import { createStackNavigator } from '@react-navigation/stack';
301
- import { trackScreenChange } from '@repliqo/sdk-react-native';
302
- import { HomeScreen } from '../screens/HomeScreen';
303
- import { ProfileScreen } from '../screens/ProfileScreen';
304
-
305
- const Tab = createBottomTabNavigator();
306
- const Stack = createStackNavigator();
307
-
308
- // Helper to dig through nested navigators and find the active route name
309
- function getActiveRouteName(state: any): string {
310
- const route = state.routes[state.index];
311
- if (route.state) return getActiveRouteName(route.state);
312
- return route.name;
313
- }
314
-
315
- export function AppNavigator() {
316
- // Keep track of the current route name between renders
317
- const currentRoute = useRef<string>('');
318
-
319
- const onReady = useCallback(() => {
320
- currentRoute.current = 'Home';
321
- // Track the initial screen (fromScreen is empty on first load)
322
- trackScreenChange('', 'Home');
323
- }, []);
324
-
325
- return (
326
- <NavigationContainer
327
- onReady={onReady}
328
- onStateChange={(state) => {
329
- if (!state) return;
330
-
331
- const newRoute = getActiveRouteName(state);
332
- if (newRoute !== currentRoute.current) {
333
- // This single call handles navigation event + screen enter/exit
334
- trackScreenChange(currentRoute.current, newRoute);
335
- currentRoute.current = newRoute;
336
- }
337
- }}
338
- >
339
- <Tab.Navigator screenOptions={{ headerShown: false }}>
340
- <Tab.Screen name="Home" component={HomeScreen} />
341
- <Tab.Screen name="Profile" component={ProfileScreen} />
342
- </Tab.Navigator>
343
- </NavigationContainer>
344
- );
345
- }
346
- ```
347
-
348
- ### What Data Gets Captured
349
-
350
- Each navigation event produces an `AnalyticsEvent` with this structure:
351
-
352
- ```typescript
353
- {
354
- type: 'navigation',
355
- screenName: 'ProfileScreen', // The destination screen
356
- data: {
357
- fromScreen: 'HomeScreen', // Where the user came from
358
- toScreen: 'ProfileScreen', // Where the user went
359
- },
360
- timestamp: '2026-03-25T14:30:00.000Z', // ISO 8601 timestamp
361
- }
362
- ```
363
-
364
- Additionally, screen visits are tracked automatically with enter/exit timestamps and duration:
365
-
366
- ```typescript
367
- {
368
- sessionId: 'abc123',
369
- screenName: 'HomeScreen',
370
- enteredAt: '2026-03-25T14:28:00.000Z',
371
- exitedAt: '2026-03-25T14:30:00.000Z',
372
- duration: 120000, // Duration in milliseconds
373
- }
374
- ```
375
-
376
- ---
377
-
378
- ## 7. Custom Events
379
-
380
- Custom events let you track specific user actions that are meaningful to your business logic -- button clicks, form submissions, feature usage, purchase completions, and anything else you want to measure.
381
-
382
- ### Basic Usage
383
-
384
- ```typescript
385
- import { AppAnalytics } from '@repliqo/sdk-react-native';
386
-
387
- // Track a simple event (name only)
388
- AppAnalytics.getInstance().trackCustomEvent('sign_up_completed');
389
-
390
- // Track an event with additional data
391
- AppAnalytics.getInstance().trackCustomEvent('item_purchased', {
392
- itemId: 'premium-plan',
393
- price: 9.99,
394
- currency: 'USD',
395
- });
396
-
397
- // Track feature usage
398
- AppAnalytics.getInstance().trackCustomEvent('dark_mode_toggled', {
399
- enabled: true,
400
- });
401
- ```
402
-
403
- ### Real-World Example: Tracking Button Clicks
404
-
405
- ```typescript
406
- // src/screens/HomeScreen.tsx
407
-
408
- import React from 'react';
409
- import { View, TouchableOpacity, Text } from 'react-native';
410
- import { AppAnalytics } from '@repliqo/sdk-react-native';
411
-
412
- export function HomeScreen() {
413
- const handleCreateHabit = () => {
414
- // Track the button click before performing the action
415
- AppAnalytics.getInstance().trackCustomEvent('create_habit_tapped', {
416
- source: 'home_screen',
417
- });
418
-
419
- // ... your actual logic to create a habit
420
- };
421
-
422
- const handleShareApp = () => {
423
- AppAnalytics.getInstance().trackCustomEvent('share_app_tapped', {
424
- source: 'home_screen',
425
- method: 'native_share',
426
- });
427
-
428
- // ... your share logic
429
- };
430
-
431
- return (
432
- <View>
433
- <TouchableOpacity onPress={handleCreateHabit}>
434
- <Text>Create Habit</Text>
435
- </TouchableOpacity>
436
- <TouchableOpacity onPress={handleShareApp}>
437
- <Text>Share App</Text>
438
- </TouchableOpacity>
439
- </View>
440
- );
441
- }
442
- ```
443
-
444
- ### Best Practices for Event Naming
445
-
446
- - Use **snake_case** for event names: `purchase_completed`, not `PurchaseCompleted` or `purchase-completed`.
447
- - Start with a **noun** followed by a **verb in past tense**: `habit_created`, `profile_updated`, `session_started`.
448
- - Keep names **short but descriptive**: `checkout_started` is better than `user_clicked_checkout_button`.
449
- - Use the `data` parameter for **variable information**: pass `itemId`, `price`, `category` as data, not as part of the event name.
450
- - Avoid **high-cardinality event names**: do not include IDs or dynamic values in the event name itself. Use `item_viewed` with `{ itemId: '123' }`, not `item_123_viewed`.
451
-
452
- ---
453
-
454
- ## 8. Error/Crash Tracking
455
-
456
- The SDK can automatically capture JavaScript errors, fatal native crashes, and unhandled Promise rejections, and send them to the Repliqo backend for analysis.
457
-
458
- ### Automatic Crash Capture
459
-
460
- When `enableCrashTracking` is set to `true` (the default), the SDK installs:
461
-
462
- 1. **A global error handler** via React Native's `ErrorUtils`. This captures all uncaught JavaScript errors, including fatal errors that would crash the app.
463
- 2. **An unhandled rejection handler** that captures Promise rejections that are not caught by a `.catch()` block.
464
-
465
- Crash reports are sent **immediately** when they occur (not batched), because the app may be about to terminate. The original React Native error handler is preserved and still called, so the standard red screen / crash behavior is unaffected.
466
-
467
- No additional setup is needed beyond enabling the flag:
468
-
469
- ```typescript
470
- AppAnalytics.init({
471
- apiKey: 'your-api-key',
472
- appId: 'your-app-id',
473
- baseUrl: 'https://api-repliqo.odmservice.site',
474
- enableCrashTracking: true, // This is the default
475
- });
476
- ```
477
-
478
- ### Manual Error Reporting
479
-
480
- For errors that you catch yourself (e.g., in try/catch blocks, error boundaries, or API call failures), use `reportError()`:
481
-
482
- ```typescript
483
- import { AppAnalytics } from '@repliqo/sdk-react-native';
484
-
485
- // Report a caught error with context metadata
486
- async function fetchUserProfile(userId: string) {
487
- try {
488
- const response = await fetch(`/api/users/${userId}`);
489
- if (!response.ok) {
490
- throw new Error(`HTTP ${response.status}: Failed to fetch profile`);
491
- }
492
- return await response.json();
493
- } catch (error) {
494
- // Report to Repliqo with extra context
495
- AppAnalytics.getInstance().reportError(error as Error, {
496
- userId,
497
- action: 'fetch_user_profile',
498
- httpStatus: (error as any).status,
499
- });
500
-
501
- // Handle the error in the UI as normal
502
- throw error;
503
- }
504
- }
505
- ```
506
-
507
- ### React Error Boundary Example
508
-
509
- ```typescript
510
- // src/components/AnalyticsErrorBoundary.tsx
511
-
512
- import React, { Component, ErrorInfo, ReactNode } from 'react';
513
- import { View, Text } from 'react-native';
514
- import { AppAnalytics } from '@repliqo/sdk-react-native';
515
-
516
- interface Props {
517
- children: ReactNode;
518
- fallback?: ReactNode;
519
- }
520
-
521
- interface State {
522
- hasError: boolean;
523
- }
524
-
525
- export class AnalyticsErrorBoundary extends Component<Props, State> {
526
- state: State = { hasError: false };
527
-
528
- static getDerivedStateFromError(): State {
529
- return { hasError: true };
530
- }
531
-
532
- componentDidCatch(error: Error, errorInfo: ErrorInfo) {
533
- // Report the error to Repliqo with component stack information
534
- try {
535
- AppAnalytics.getInstance().reportError(error, {
536
- componentStack: errorInfo.componentStack,
537
- source: 'error_boundary',
538
- });
539
- } catch {
540
- // SDK may not be initialized yet
541
- }
542
- }
543
-
544
- render() {
545
- if (this.state.hasError) {
546
- return this.props.fallback || (
547
- <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
548
- <Text>Something went wrong.</Text>
549
- </View>
550
- );
551
- }
552
- return this.props.children;
553
- }
554
- }
555
- ```
556
-
557
- ### What Data Is Captured in a Crash Report
558
-
559
- Each crash report contains the following fields:
560
-
561
- | Field | Type | Description |
562
- |---|---|---|
563
- | `sessionId` | `string` or `undefined` | The active session ID, if a session is running. |
564
- | `type` | `'js_error'` \| `'native_crash'` \| `'unhandled_rejection'` | The category of error. Fatal errors from `ErrorUtils` are reported as `native_crash`. |
565
- | `message` | `string` | The error message (e.g., `"Cannot read property 'name' of undefined"`). |
566
- | `stackTrace` | `string` or `undefined` | The JavaScript stack trace, if available. |
567
- | `screenName` | `string` or `undefined` | The screen the user was on when the crash occurred. |
568
- | `deviceInfo` | `Record<string, any>` or `undefined` | Optional device context. |
569
- | `metadata` | `Record<string, any>` or `undefined` | Any additional metadata you pass to `reportError()`. For automatic captures, this includes `{ isFatal: boolean }`. |
570
- | `timestamp` | `string` | ISO 8601 timestamp of when the error occurred. |
571
-
572
- ---
573
-
574
- ## 9. Manual Screen Tracking
575
-
576
- In most cases, the automatic navigation tracker (Section 6) handles screen tracking for you. However, there are scenarios where you need manual control:
577
-
578
- - Screens not managed by React Navigation (e.g., modals, bottom sheets, onboarding flows).
579
- - Custom navigation systems that do not use `@react-navigation/native`.
580
- - Conditional screens that appear within a single navigator route.
581
-
582
- ### Using `trackScreenEnter()` and `trackScreenExit()`
583
-
584
- ```typescript
585
- import { trackScreenEnter, trackScreenExit } from '@repliqo/sdk-react-native';
586
-
587
- // Call when the screen becomes visible
588
- trackScreenEnter('OnboardingStep1');
589
-
590
- // Call when the screen is no longer visible
591
- trackScreenExit('OnboardingStep1');
592
- ```
593
-
594
- ### Example: Tracking a Modal Screen
595
-
596
- ```typescript
597
- // src/components/PremiumModal.tsx
598
-
599
- import React, { useEffect } from 'react';
600
- import { Modal, View, Text } from 'react-native';
601
- import { trackScreenEnter, trackScreenExit } from '@repliqo/sdk-react-native';
602
-
603
- interface PremiumModalProps {
604
- visible: boolean;
605
- onClose: () => void;
606
- }
607
-
608
- export function PremiumModal({ visible, onClose }: PremiumModalProps) {
609
- useEffect(() => {
610
- if (visible) {
611
- // Register this modal as a screen visit when it opens
612
- trackScreenEnter('PremiumUpgradeModal');
613
- } else {
614
- // Record the exit (and duration) when it closes
615
- trackScreenExit('PremiumUpgradeModal');
616
- }
617
- }, [visible]);
618
-
619
- return (
620
- <Modal visible={visible} onRequestClose={onClose}>
621
- <View>
622
- <Text>Upgrade to Premium</Text>
623
- {/* ... modal content ... */}
624
- </View>
625
- </Modal>
626
- );
627
- }
628
- ```
629
-
630
- ### When to Use Manual vs. Automatic
631
-
632
- | Scenario | Recommended Approach |
633
- |---|---|
634
- | Standard React Navigation stack/tab screens | Automatic (`useNavigationTracker` or `trackScreenChange`) |
635
- | Modals and bottom sheets | Manual (`trackScreenEnter` / `trackScreenExit`) |
636
- | Onboarding wizard steps within a single route | Manual |
637
- | Custom navigation (no React Navigation) | Manual |
638
- | WebView content within a native screen | Manual (track the WebView "page" as a screen) |
639
-
640
- ---
641
-
642
- ## 10. Session Management
643
-
644
- A session represents a single continuous usage period of your app by a user. Sessions tie all events, screen visits, and crash reports together.
645
-
646
- ### How Sessions Work
647
-
648
- 1. **Start**: You call `startSession()` with a device identifier and optional device info. The backend creates a session record and returns a session ID.
649
- 2. **Track**: While the session is active, all events (navigation, touch, custom, screen visits) are associated with this session ID.
650
- 3. **Flush**: Events are periodically flushed to the backend in batches. An automatic flush also happens when the app goes to the background.
651
- 4. **End**: You call `endSession()` (or let the SDK handle it on destroy). This flushes remaining events and tells the backend the session is complete.
652
-
653
- ### Starting a Session
654
-
655
- ```typescript
656
- import { AppAnalytics } from '@repliqo/sdk-react-native';
657
- import { Platform, Dimensions } from 'react-native';
658
-
659
- const analytics = AppAnalytics.getInstance();
660
-
661
- // Generate or retrieve a persistent device ID
662
- const deviceId = 'unique-device-identifier';
663
-
664
- // Gather device info (see Section 11 for a complete example)
665
- const { width, height } = Dimensions.get('window');
666
- const deviceInfo = {
667
- os: Platform.OS,
668
- osVersion: String(Platform.Version),
669
- model: 'Pixel 7',
670
- brand: 'Google',
671
- screenWidth: width,
672
- screenHeight: height,
673
- appVersion: '2.1.0',
674
- };
675
-
676
- // startSession returns the session ID assigned by the backend
677
- const sessionId = await analytics.startSession(deviceId, deviceInfo);
678
- console.log('Session ID:', sessionId);
679
- ```
680
-
681
- ### Ending a Session
682
-
683
- ```typescript
684
- // End the session -- this flushes all pending events first
685
- await AppAnalytics.getInstance().endSession();
686
- ```
687
-
688
- Ending a session:
689
-
690
- 1. Stops snapshot capture (if enabled).
691
- 2. Records the exit for the current screen (if the user is on one).
692
- 3. Flushes all queued events, screen visits, and snapshots.
693
- 4. Notifies the backend that the session has ended.
694
- 5. Clears the session ID and current screen state.
695
-
696
- ### Checking Session State
697
-
698
- ```typescript
699
- const analytics = AppAnalytics.getInstance();
700
-
701
- // Get the current session ID (null if no session is active)
702
- const sessionId = analytics.getSessionId();
703
-
704
- // Get the name of the screen the user is currently on
705
- const currentScreen = analytics.getCurrentScreen();
706
- ```
707
-
708
- ### Session Lifecycle Diagram
709
-
710
- ```
711
- App Launch
712
- |
713
- v
714
- AppAnalytics.init(config) --> SDK initialized, queues created
715
- |
716
- v
717
- analytics.startSession() --> Backend creates session, returns ID
718
- |
719
- v
720
- [User interacts with app] --> Events buffered in memory
721
- |
722
- v
723
- [Batch threshold reached] --> Events sent to backend
724
- | (automatic, every flushInterval or batchSize)
725
- v
726
- [App goes to background] --> Automatic flush triggered
727
- |
728
- v
729
- [App returns to foreground] --> Tracking resumes (same session)
730
- |
731
- v
732
- analytics.endSession() --> Final flush, session closed on backend
733
- |
734
- v
735
- AppAnalytics.destroy() --> Cleanup, listeners removed
736
- ```
737
-
738
- ---
739
-
740
- ## 11. Device Info
741
-
742
- The `DeviceInfo` object provides context about the user's device with each session. This data appears in the dashboard alongside session details and crash reports.
743
-
744
- ### Basic Device Info (No Extra Dependencies)
745
-
746
- Using only React Native's built-in APIs:
747
-
748
- ```typescript
749
- import { Platform, Dimensions } from 'react-native';
750
- import type { DeviceInfo } from '@repliqo/sdk-react-native';
751
-
752
- function getBasicDeviceInfo(): DeviceInfo {
753
- const { width, height } = Dimensions.get('window');
754
-
755
- return {
756
- os: Platform.OS, // 'ios' or 'android'
757
- osVersion: String(Platform.Version), // e.g., '33' (Android) or '17.2' (iOS)
758
- model: 'Unknown', // Not available without a library
759
- brand: 'Unknown', // Not available without a library
760
- screenWidth: width, // e.g., 393
761
- screenHeight: height, // e.g., 852
762
- appVersion: '1.0.0', // Hardcode or read from your config
763
- };
764
- }
765
- ```
766
-
767
- ### Enhanced Device Info with `react-native-device-info`
768
-
769
- For richer device data, install [react-native-device-info](https://github.com/react-native-device-info/react-native-device-info):
770
-
771
- ```bash
772
- npm install react-native-device-info
773
- ```
774
-
775
- Then use it to populate the fields:
776
-
777
- ```typescript
778
- import { Platform, Dimensions } from 'react-native';
779
- import DeviceInfoLib from 'react-native-device-info';
780
- import type { DeviceInfo } from '@repliqo/sdk-react-native';
781
-
782
- async function getEnhancedDeviceInfo(): Promise<DeviceInfo> {
783
- const { width, height } = Dimensions.get('window');
784
-
785
- return {
786
- os: Platform.OS,
787
- osVersion: String(Platform.Version),
788
- model: DeviceInfoLib.getModel(), // e.g., 'Pixel 7'
789
- brand: DeviceInfoLib.getBrand(), // e.g., 'Google'
790
- screenWidth: width,
791
- screenHeight: height,
792
- appVersion: DeviceInfoLib.getVersion(), // e.g., '2.1.0'
793
- };
794
- }
795
- ```
796
-
797
- ### Generating a Persistent Device ID
798
-
799
- For the `deviceId` parameter in `startSession()`, you need a value that is unique per device and persists across app launches. Here are some approaches:
800
-
801
- ```typescript
802
- import DeviceInfoLib from 'react-native-device-info';
803
-
804
- // Option 1: Use react-native-device-info's unique ID (recommended)
805
- const deviceId = await DeviceInfoLib.getUniqueId();
806
-
807
- // Option 2: Generate a random ID and store it with AsyncStorage
808
- import AsyncStorage from '@react-native-async-storage/async-storage';
809
-
810
- async function getOrCreateDeviceId(): Promise<string> {
811
- let deviceId = await AsyncStorage.getItem('repliqo_device_id');
812
- if (!deviceId) {
813
- deviceId = `${Platform.OS}-${Math.random().toString(36).slice(2, 10)}`;
814
- await AsyncStorage.setItem('repliqo_device_id', deviceId);
815
- }
816
- return deviceId;
817
- }
818
- ```
819
-
820
- ---
821
-
822
- ## 12. Advanced: Flush Control
823
-
824
- The SDK batches events in memory and sends them to the backend periodically. You can control when flushes happen.
825
-
826
- ### Automatic Flushing
827
-
828
- By default, the SDK flushes automatically in two situations:
829
-
830
- 1. **Timer-based**: Every `flushInterval` milliseconds (default: 30,000ms), all queued events are sent.
831
- 2. **App background**: When the app transitions to the `background` or `inactive` state, a flush is triggered immediately. This is handled internally by the SDK via an `AppState` listener and requires no action from you.
832
-
833
- ### Manual Flushing
834
-
835
- You can trigger a flush at any time:
836
-
837
- ```typescript
838
- import { AppAnalytics } from '@repliqo/sdk-react-native';
839
-
840
- // Flush all pending events, screen visits, and snapshots immediately
841
- await AppAnalytics.getInstance().flush();
842
- ```
843
-
844
- This is useful in scenarios like:
845
-
846
- - Before navigating to a payment screen (ensure all events are sent).
847
- - After a critical user action that you want to see in the dashboard right away.
848
- - In a "log out" flow before ending the session.
849
-
850
- ### Tuning `batchSize` and `flushInterval`
851
-
852
- | Use Case | Recommended `batchSize` | Recommended `flushInterval` |
853
- |---|---|---|
854
- | **Low-traffic app** (few screens, few events) | `10` | `60000` (60s) |
855
- | **Standard app** | `20` (default) | `30000` (30s, default) |
856
- | **High-traffic app** (many touch events, frequent navigation) | `50` | `15000` (15s) |
857
- | **Real-time monitoring** (debugging, demo) | `5` | `5000` (5s) |
858
-
859
- Lower `batchSize` and `flushInterval` values mean more frequent network requests but lower risk of data loss. Higher values reduce network usage but events take longer to appear in the dashboard.
860
-
861
- ---
862
-
863
- ## 13. Advanced: Debug Mode
864
-
865
- Debug mode enables verbose console logging from the SDK, which is essential during development and integration testing.
866
-
867
- ### Enabling Debug Mode
868
-
869
- ```typescript
870
- AppAnalytics.init({
871
- apiKey: 'your-api-key',
872
- appId: 'your-app-id',
873
- baseUrl: 'https://api-repliqo.odmservice.site',
874
- debug: true, // Enable verbose logging
875
- });
876
- ```
877
-
878
- A common pattern is to tie debug mode to the development build:
879
-
880
- ```typescript
881
- debug: __DEV__, // true in development, false in production
882
- ```
883
-
884
- ### What Logs to Expect
885
-
886
- With debug mode enabled, you will see console output for every SDK operation:
887
-
888
- ```
889
- [Repliqo] SDK initialized with config: { baseUrl: "...", appId: "myapp", batchSize: 20, ... }
890
- [Repliqo] Session started with ID: sess_abc123def456
891
- [Repliqo] Screen entered: HomeScreen
892
- [Repliqo] Navigation tracked: HomeScreen -> ProfileScreen
893
- [Repliqo] Screen exited: HomeScreen duration: 4523
894
- [Repliqo] Screen entered: ProfileScreen
895
- [Repliqo] Custom event tracked: settings_opened
896
- [Repliqo] Touch tracked: { type: "touch", screenName: "ProfileScreen", data: { x: 195, y: 420 } }
897
- [Repliqo] Flushing queues...
898
- [Repliqo] Flush complete
899
- [Repliqo] App going to background/inactive, flushing queues
900
- ```
901
-
902
- ### How to Verify Events Are Being Sent
903
-
904
- 1. **Enable debug mode** and watch the console for "Flushing queues..." followed by "Flush complete" without errors.
905
- 2. **Check the dashboard**: Go to [https://repliqo.odmservice.site](https://repliqo.odmservice.site) and look for your session in the Sessions list. Events should appear within a few seconds of a flush.
906
- 3. **Monitor network requests**: Use React Native Debugger or Flipper to inspect outgoing HTTP requests to your `baseUrl`. You should see POST requests to endpoints like `/sessions`, `/events/batch`, and `/screen-visits/batch`.
907
- 4. **Force a flush**: Call `AppAnalytics.getInstance().flush()` from a debug button in your app to send all queued events immediately.
908
-
909
- ---
910
-
911
- ## 14. Dashboard Guide
912
-
913
- The Repliqo dashboard at [https://repliqo.odmservice.site](https://repliqo.odmservice.site) provides a visual interface for all the data your SDK collects. Here is a brief overview of the main sections.
914
-
915
- ### Sessions
916
-
917
- The sessions view lists all recorded user sessions with:
918
-
919
- - **Device info**: OS, OS version, device model, brand, screen dimensions.
920
- - **Duration**: How long the session lasted.
921
- - **Screen count**: Number of screens visited during the session.
922
- - **Event count**: Total events tracked (navigation, touch, custom).
923
- - **Timestamps**: Start and end times.
924
-
925
- Click on a session to see its full timeline of events.
926
-
927
- ### Screen Visit Analytics
928
-
929
- View aggregate data about which screens users visit most often:
930
-
931
- - **Screen popularity**: Ranked list of screens by visit count.
932
- - **Average duration**: How long users spend on each screen.
933
- - **Entry/exit points**: Which screens users land on first and leave from.
934
-
935
- ### Heatmaps
936
-
937
- When touch tracking is enabled, the dashboard generates visual heatmaps showing where users tap on each screen:
938
-
939
- - **Tap density**: Color-coded overlay showing hotspots of user interaction.
940
- - **Per-screen breakdown**: View heatmaps for individual screens.
941
- - **Session-level replay**: See the exact touch sequence for a specific session.
942
-
943
- ### Crash Reports
944
-
945
- The crash reports section aggregates all errors reported by the SDK:
946
-
947
- - **Error grouping**: Similar errors are grouped together by message and stack trace.
948
- - **Occurrence count**: How many times each error has occurred.
949
- - **Affected sessions**: Which sessions experienced the error.
950
- - **Stack traces**: Full JavaScript stack traces for debugging.
951
- - **Context**: The screen the user was on, device info, and any metadata you attached.
952
-
953
- ### Funnels
954
-
955
- Define multi-step funnels to track conversion through specific user flows:
956
-
957
- - Configure a sequence of screens or events.
958
- - See drop-off rates at each step.
959
- - Compare funnel performance across time periods.
960
-
961
- ### Data Export
962
-
963
- Export your analytics data for use in external tools:
964
-
965
- - Export sessions, events, or crash reports as CSV or JSON.
966
- - Filter by date range, app version, or device type before exporting.
967
-
968
- ---
969
-
970
- ## 15. API Reference
971
-
972
- Quick reference for all public methods on the `AppAnalytics` class.
973
-
974
- ### Static Methods
975
-
976
- | Method | Signature | Returns | Description |
977
- |---|---|---|---|
978
- | `init` | `init(config: SDKConfig)` | `AppAnalytics` | Initializes the SDK with the given configuration. Returns the singleton instance. Safe to call multiple times (subsequent calls return the existing instance). |
979
- | `getInstance` | `getInstance()` | `AppAnalytics` | Returns the singleton instance. Throws an error if `init()` has not been called. |
980
- | `destroy` | `destroy()` | `void` | Tears down the SDK: stops error tracking and snapshot capture, stops auto-flush timers, performs a final flush (fire-and-forget), removes the AppState listener, and nullifies the singleton. |
981
-
982
- ### Instance Methods
983
-
984
- | Method | Signature | Returns | Description |
985
- |---|---|---|---|
986
- | `startSession` | `startSession(deviceId: string, deviceInfo?: DeviceInfo)` | `Promise<string>` | Starts a new session with the backend. Returns the session ID. Sets up error tracker and snapshot capture for this session. |
987
- | `endSession` | `endSession()` | `Promise<void>` | Ends the current session. Flushes all pending data, records the final screen exit, and notifies the backend. |
988
- | `trackTouch` | `trackTouch(x: number, y: number, screenName?: string, extra?: Record<string, any>)` | `void` | Records a touch event at the given coordinates. If `screenName` is omitted, uses the current screen. Only works when `enableTouchTracking` is `true`. |
989
- | `trackNavigation` | `trackNavigation(fromScreen: string, toScreen: string)` | `void` | Records a navigation event between two screens. Only works when `enableNavigationTracking` is `true`. |
990
- | `trackCustomEvent` | `trackCustomEvent(eventName: string, data?: Record<string, any>)` | `void` | Records a custom event with an arbitrary name and optional data payload. |
991
- | `reportError` | `reportError(error: Error, metadata?: Record<string, any>)` | `void` | Manually reports a caught error. Only works when `enableCrashTracking` is `true`. The error is sent immediately, not batched. |
992
- | `onScreenEnter` | `onScreenEnter(screenName: string)` | `void` | Marks the user as having entered a screen. Starts the visit timer. Updates the error tracker's current screen context. |
993
- | `onScreenExit` | `onScreenExit(screenName: string)` | `void` | Marks the user as having exited a screen. Records a `ScreenVisit` with the duration in the screen visit queue. |
994
- | `captureSnapshot` | `captureSnapshot(tree: ComponentNode, screenName?: string)` | `void` | Manually captures a component tree snapshot. Only works when `enableSnapshots` is `true` and a session is active. |
995
- | `flush` | `flush()` | `Promise<void>` | Immediately sends all queued events, screen visits, and snapshots to the backend. |
996
- | `getSessionId` | `getSessionId()` | `string \| null` | Returns the current session ID, or `null` if no session is active. |
997
- | `getCurrentScreen` | `getCurrentScreen()` | `string \| null` | Returns the name of the screen the user is currently on, or `null`. |
998
- | `isInitialized` | `isInitialized()` | `boolean` | Returns `true` if the SDK instance exists and is ready. |
999
-
1000
- ### Standalone Functions
1001
-
1002
- | Function | Signature | Description |
1003
- |---|---|---|
1004
- | `useNavigationTracker` | `useNavigationTracker()` | React hook that returns `{ navigationRef, onReady, onStateChange }` for automatic navigation tracking with `NavigationContainer`. |
1005
- | `trackScreenChange` | `trackScreenChange(fromScreen: string, toScreen: string)` | Convenience function that calls `trackNavigation`, `onScreenExit`, and `onScreenEnter` in sequence. |
1006
- | `trackScreenEnter` | `trackScreenEnter(screenName: string)` | Standalone function to mark screen entry. Silently no-ops if SDK is not initialized. |
1007
- | `trackScreenExit` | `trackScreenExit(screenName: string)` | Standalone function to mark screen exit. Silently no-ops if SDK is not initialized. |
1008
-
1009
- ---
1010
-
1011
- ## 16. Screen Capture Architecture
1012
-
1013
- The SDK uses a **two-tier capture strategy** for session replay screenshots:
1014
-
1015
- ### Tier 1: Native Multi-Window Capture (Android)
1016
-
1017
- The SDK includes a native Android module (`ScreenCaptureModule`) that captures **all visible app windows** — including modals, alerts, system dialogs, keyboard overlays, and toasts.
1018
-
1019
- **How it works:**
1020
- - Uses `WindowManagerGlobal` (via reflection) to enumerate all root views in the process
1021
- - Draws each visible view to a shared `Canvas` using `View.draw()`, composited in z-order
1022
- - Scales to 390px width, compresses to JPEG at 40% quality
1023
- - Returns base64-encoded JPEG to JavaScript
1024
-
1025
- **No permissions required.** No `MediaProjection`, no foreground service, no system dialogs.
1026
-
1027
- ### Tier 2: react-native-view-shot (Fallback)
1028
-
1029
- If the native module is unavailable (iOS, or if the module fails to load), the SDK falls back to `react-native-view-shot`'s `captureScreen()`. This captures only the main React Native view — **modals and native dialogs are NOT included** in the fallback.
1030
-
1031
- ### Capture Priority
1032
-
1033
- ```
1034
- captureScreenshot()
1035
- ├─ Try: Native multi-window capture (Android)
1036
- │ └─ Returns: full screenshot including modals/alerts
1037
- ├─ Fallback: react-native-view-shot
1038
- │ └─ Returns: main view only (no modals)
1039
- └─ Last resort: null (frame dropped)
1040
- ```
1041
-
1042
- Each frame includes a `source` field (`'native'` or `'viewshot'`) so the replay player knows which method produced it.
1043
-
1044
- ### iOS Support
1045
-
1046
- Native multi-window capture is not yet implemented for iOS. iOS sessions use `react-native-view-shot` exclusively. This is a known limitation documented for future development.
1047
-
1048
- ---
1049
-
1050
- ## 17. Troubleshooting
1051
-
1052
- ### "Failed to start session"
1053
-
1054
- **Symptom**: `startSession()` throws an error or the console shows `[Repliqo] Init failed`.
1055
-
1056
- **Possible causes and fixes**:
1057
-
1058
- - **Wrong `baseUrl`**: Verify the URL is correct. For Android emulator, use `http://10.0.2.2:<port>` (not `localhost`). For iOS simulator, use `http://localhost:<port>`.
1059
- - **Invalid API key**: Double-check your `apiKey` matches the one in the Repliqo dashboard.
1060
- - **Network issue**: Ensure the device/emulator has network access. Try opening the `baseUrl` in a browser.
1061
- - **Backend not running**: If developing locally, confirm the Repliqo API server is running.
1062
-
1063
- ### Events Not Appearing in the Dashboard
1064
-
1065
- **Symptom**: You see "Session started" in the logs, but events do not show up in the dashboard.
1066
-
1067
- **Possible causes and fixes**:
1068
-
1069
- - **Events are still queued**: The SDK buffers events and sends them in batches. Wait for the `flushInterval` (default 30s) or call `flush()` manually.
1070
- - **`batchSize` too large**: If your `batchSize` is set very high and users don't generate that many events, the batch may never fill up before the flush interval. Reduce `batchSize` or `flushInterval`.
1071
- - **Session not started**: Events are silently dropped if no session is active. Check that `startSession()` completed successfully.
1072
- - **Feature disabled**: If `enableNavigationTracking` is `false`, navigation events are silently ignored. Same for `enableTouchTracking` and touch events.
1073
-
1074
- ### Metro Can't Resolve the Module
1075
-
1076
- **Symptom**: Error like `Unable to resolve module @repliqo/sdk-react-native`.
1077
-
1078
- **Fix**: Clear the Metro bundler cache:
1079
-
1080
- ```bash
1081
- npx react-native start --reset-cache
1082
- ```
1083
-
1084
- If installing from a local path, make sure the path in `package.json` is correct and that the SDK has been built:
1085
-
1086
- ```bash
1087
- cd /path/to/sdk-react-native
1088
- npm run build
1089
- ```
1090
-
1091
- ### Touch Events Not Working
1092
-
1093
- **Symptom**: Touch tracking is enabled but no touch events appear.
1094
-
1095
- **Possible causes and fixes**:
1096
-
1097
- - **`enableTouchTracking: false`**: Verify this is set to `true` in your config.
1098
- - **TouchTracker wrapper missing**: The `TouchTracker` component must wrap your app's component tree to intercept touch events. If you are not using it, touches will not be captured. Consider using manual `trackTouch()` calls as an alternative.
1099
- - **Known limitation**: The `TouchTracker` component can sometimes interfere with gesture handlers (e.g., `react-native-gesture-handler`). If you experience issues, disable `enableTouchTracking` and use manual `trackTouch()` calls for specific interactions instead.
1100
-
1101
- ### High Memory Usage
1102
-
1103
- **Symptom**: The app consumes more memory than expected after integrating the SDK.
1104
-
1105
- **Possible causes and fixes**:
1106
-
1107
- - **Snapshots enabled**: Snapshot capture serializes the component tree at regular intervals. This can be memory-intensive. Disable snapshots (`enableSnapshots: false`) if you do not need session replay.
1108
- - **Large `batchSize`**: A very large batch size means more events are held in memory before being sent. Reduce `batchSize` to flush more frequently.
1109
- - **Long sessions**: The `maxSnapshotsPerSession` limit (default 3600) prevents unbounded snapshot growth, but screen visits and events still accumulate. Consider calling `flush()` periodically for very long sessions.
1110
-
1111
- ### Crashes Not Being Reported
1112
-
1113
- **Symptom**: `enableCrashTracking` is `true`, but no crash reports appear in the dashboard.
1114
-
1115
- **Possible causes and fixes**:
1116
-
1117
- - **Session not started**: Crash reports include the session ID for context, but they are sent even without a session. Check that the SDK is initialized (not just the session).
1118
- - **Network failure on crash**: If the app crashes before the crash report HTTP request completes, the report may be lost. This is inherent to crash reporting in JavaScript -- native crash reporting libraries handle this better.
1119
- - **Error caught elsewhere**: If another error handler (e.g., Sentry, Bugsnag) catches the error first and does not rethrow it, Repliqo may not see it. The SDK preserves the original error handler chain, so this should not normally be an issue.
1120
-
1121
- ---
1122
-
1123
- ## 17. TypeScript Types
1124
-
1125
- All types are exported from the main package entry point. You can import them with:
1126
-
1127
- ```typescript
1128
- import type {
1129
- EventType,
1130
- AnalyticsEvent,
1131
- TouchEventData,
1132
- NavigationEventData,
1133
- ScreenVisit,
1134
- DeviceInfo,
1135
- SDKConfig,
1136
- ComponentNode,
1137
- SnapshotData,
1138
- SnapshotDiff,
1139
- DiffPatch,
1140
- SnapshotPayload,
1141
- CrashReport,
1142
- ResolvedConfig,
1143
- SnapshotCaptureConfig,
1144
- } from '@repliqo/sdk-react-native';
1145
- ```
1146
-
1147
- ### `EventType`
1148
-
1149
- ```typescript
1150
- type EventType = 'touch' | 'scroll' | 'gesture' | 'navigation' | 'custom';
1151
- ```
1152
-
1153
- ### `AnalyticsEvent`
1154
-
1155
- ```typescript
1156
- interface AnalyticsEvent {
1157
- type: EventType;
1158
- screenName?: string;
1159
- data: Record<string, any>;
1160
- timestamp: string; // ISO 8601
1161
- }
1162
- ```
1163
-
1164
- ### `TouchEventData`
1165
-
1166
- ```typescript
1167
- interface TouchEventData {
1168
- x: number;
1169
- y: number;
1170
- elementTag?: string;
1171
- elementText?: string;
1172
- }
1173
- ```
1174
-
1175
- ### `NavigationEventData`
1176
-
1177
- ```typescript
1178
- interface NavigationEventData {
1179
- fromScreen: string;
1180
- toScreen: string;
1181
- }
1182
- ```
1183
-
1184
- ### `ScreenVisit`
1185
-
1186
- ```typescript
1187
- interface ScreenVisit {
1188
- sessionId: string;
1189
- screenName: string;
1190
- enteredAt: string; // ISO 8601
1191
- exitedAt?: string; // ISO 8601
1192
- duration?: number; // Milliseconds
1193
- }
1194
- ```
1195
-
1196
- ### `DeviceInfo`
1197
-
1198
- ```typescript
1199
- interface DeviceInfo {
1200
- os: string;
1201
- osVersion: string;
1202
- model: string;
1203
- brand: string;
1204
- screenWidth: number;
1205
- screenHeight: number;
1206
- appVersion?: string;
1207
- }
1208
- ```
1209
-
1210
- ### `SDKConfig`
1211
-
1212
- ```typescript
1213
- interface SDKConfig {
1214
- apiKey: string;
1215
- appId: string;
1216
- baseUrl: string;
1217
- batchSize?: number;
1218
- flushInterval?: number;
1219
- enableTouchTracking?: boolean;
1220
- enableNavigationTracking?: boolean;
1221
- enableSnapshots?: boolean;
1222
- snapshotInterval?: number;
1223
- maxSnapshotsPerSession?: number;
1224
- enableCrashTracking?: boolean;
1225
- debug?: boolean;
1226
- }
1227
- ```
1228
-
1229
- ### `CrashReport`
1230
-
1231
- ```typescript
1232
- interface CrashReport {
1233
- sessionId?: string;
1234
- type: 'js_error' | 'native_crash' | 'unhandled_rejection';
1235
- message: string;
1236
- stackTrace?: string;
1237
- screenName?: string;
1238
- deviceInfo?: Record<string, any>;
1239
- metadata?: Record<string, any>;
1240
- timestamp: string; // ISO 8601
1241
- }
1242
- ```
1243
-
1244
- ### `ComponentNode`
1245
-
1246
- ```typescript
1247
- interface ComponentNode {
1248
- type: string;
1249
- props: Record<string, any>;
1250
- text?: string;
1251
- children?: ComponentNode[];
1252
- bounds?: {
1253
- x: number;
1254
- y: number;
1255
- width: number;
1256
- height: number;
1257
- };
1258
- }
1259
- ```
1260
-
1261
- ### `SnapshotData`
1262
-
1263
- ```typescript
1264
- interface SnapshotData {
1265
- screenName: string;
1266
- timestamp: string;
1267
- sequence: number;
1268
- isDiff: boolean;
1269
- tree: ComponentNode | SnapshotDiff;
1270
- screenDimensions: {
1271
- width: number;
1272
- height: number;
1273
- };
1274
- }
1275
- ```
1276
-
1277
- ### `SnapshotDiff`
1278
-
1279
- ```typescript
1280
- interface SnapshotDiff {
1281
- type: 'diff';
1282
- patches: DiffPatch[];
1283
- }
1284
- ```
1285
-
1286
- ### `DiffPatch`
1287
-
1288
- ```typescript
1289
- interface DiffPatch {
1290
- path: string;
1291
- op: 'add' | 'remove' | 'replace';
1292
- value?: any;
1293
- oldValue?: any;
1294
- }
1295
- ```
1296
-
1297
- ### `SnapshotPayload`
1298
-
1299
- ```typescript
1300
- interface SnapshotPayload {
1301
- sessionId: string;
1302
- screenName: string;
1303
- timestamp: string;
1304
- sequence: number;
1305
- isDiff: boolean;
1306
- data: Record<string, any>;
1307
- }
1308
- ```
1309
-
1310
- ---
1311
-
1312
- **Need help?** Open an issue on the Repliqo repository or reach out through the dashboard at [https://repliqo.odmservice.site](https://repliqo.odmservice.site).
1
+ # Repliqo SDK for React Native - Integration Guide
2
+
3
+ > **Version:** 0.1.0
4
+ > **Package:** `@repliqo/sdk-react-native`
5
+ > **Last updated:** March 2026
6
+
7
+ ---
8
+
9
+ ## Table of Contents
10
+
11
+ 1. [Overview](#1-overview)
12
+ 2. [Prerequisites](#2-prerequisites)
13
+ 3. [Installation](#3-installation)
14
+ 4. [Quick Start (5-Minute Setup)](#4-quick-start-5-minute-setup)
15
+ 5. [Configuration Reference](#5-configuration-reference)
16
+ 6. [Navigation Tracking (Automatic)](#6-navigation-tracking-automatic)
17
+ 7. [Custom Events](#7-custom-events)
18
+ 8. [Error/Crash Tracking](#8-errorcrash-tracking)
19
+ 9. [Manual Screen Tracking](#9-manual-screen-tracking)
20
+ 10. [Session Management](#10-session-management)
21
+ 11. [Device Info](#11-device-info)
22
+ 12. [Advanced: Flush Control](#12-advanced-flush-control)
23
+ 13. [Advanced: Debug Mode](#13-advanced-debug-mode)
24
+ 14. [Dashboard Guide](#14-dashboard-guide)
25
+ 15. [API Reference](#15-api-reference)
26
+ 16. [Screen Capture Architecture](#16-screen-capture-architecture)
27
+ 17. [Troubleshooting](#17-troubleshooting)
28
+ 17. [TypeScript Types](#17-typescript-types)
29
+
30
+ ---
31
+
32
+ ## 1. Overview
33
+
34
+ Repliqo is a lightweight analytics SDK designed specifically for React Native applications. It provides developers with deep visibility into how users interact with their mobile apps by capturing navigation patterns, screen visits, touch events, crashes, and custom events -- all with minimal performance overhead and zero impact on your app's startup time.
35
+
36
+ The SDK works on a buffer-and-batch architecture: events are collected in memory as users interact with your app, then sent to the Repliqo backend in efficient batches. The backend stores and processes this data, which you can then explore through the Repliqo dashboard. The dashboard provides session replays, heatmaps, funnel analysis, crash reports, and data export capabilities. Because events are batched rather than sent one-by-one, the SDK keeps network usage low and never blocks the main thread.
37
+
38
+ ---
39
+
40
+ ## 2. Prerequisites
41
+
42
+ Before integrating the Repliqo SDK, make sure your project meets the following requirements:
43
+
44
+ - **React Native** >= 0.68.0
45
+ - **React** >= 17.0.0
46
+ - **@react-navigation/native** >= 6.0.0 (required only if you want automatic navigation tracking)
47
+ - **A Repliqo account with an API key**
48
+
49
+ ### Getting Your API Key
50
+
51
+ 1. Go to the Repliqo dashboard at **[https://repliqo.odmservice.site](https://repliqo.odmservice.site)**.
52
+ 2. Sign in or create a new account.
53
+ 3. Navigate to **Settings > Applications** and click **Create Application**.
54
+ 4. Give your app a name (this becomes your `appId`) and note the **API key** that is generated.
55
+ 5. Copy both the `appId` and the `apiKey` -- you will need them during SDK initialization.
56
+
57
+ ---
58
+
59
+ ## 3. Installation
60
+
61
+ ### From a Local Path (Development)
62
+
63
+ If you have the SDK source code locally (e.g., in a monorepo or cloned repository), install it by referencing the local path:
64
+
65
+ ```bash
66
+ npm install file:../path/to/sdk-react-native
67
+ ```
68
+
69
+ For example, if your project structure looks like this:
70
+
71
+ ```
72
+ my-workspace/
73
+ app-analytics-api/
74
+ packages/
75
+ sdk-react-native/ # <-- The SDK
76
+ my-react-native-app/ # <-- Your app
77
+ ```
78
+
79
+ Run this from your React Native app directory:
80
+
81
+ ```bash
82
+ npm install file:../app-analytics-api/packages/sdk-react-native
83
+ ```
84
+
85
+ ### From npm (Future)
86
+
87
+ Once the SDK is published to npm, you will be able to install it with:
88
+
89
+ ```bash
90
+ npm install @repliqo/sdk-react-native
91
+ ```
92
+
93
+ ### Post-Installation
94
+
95
+ After installing, if Metro bundler has trouble resolving the module, clear the cache:
96
+
97
+ ```bash
98
+ npx react-native start --reset-cache
99
+ ```
100
+
101
+ The SDK includes a native Android module for **multi-window screen capture** (captures modals, alerts, and dialogs). It autolinks automatically via React Native CLI — no manual linking, no `settings.gradle` edits, no `MainApplication` changes needed.
102
+
103
+ On Android, the native module uses `View.draw()` on all app windows to composite a full screenshot including overlays. **No permissions are required** — it does not use MediaProjection or screen recording APIs.
104
+
105
+ On iOS, the native module is not yet implemented. The SDK falls back to `react-native-view-shot` which captures the main view only.
106
+
107
+ > **Note:** After installing, you must rebuild the native app (`npx react-native run-android`) for the native module to be linked. A Metro-only restart is not sufficient.
108
+
109
+ ---
110
+
111
+ ## 4. Quick Start (5-Minute Setup)
112
+
113
+ This section gets you up and running with the absolute minimum configuration. After completing these two steps, navigation tracking and screen visit analytics will work automatically.
114
+
115
+ ### Step 1: Create the Analytics Service File
116
+
117
+ Create a file at `src/services/repliqo.ts` in your project:
118
+
119
+ ```typescript
120
+ // src/services/repliqo.ts
121
+
122
+ /**
123
+ * Repliqo Analytics SDK - Initialization and session management.
124
+ *
125
+ * This file initializes the SDK and starts a session with device info.
126
+ * Import and call initRepliqo() from your App.tsx on startup.
127
+ */
128
+ import { AppAnalytics } from '@repliqo/sdk-react-native';
129
+ import { Platform, Dimensions } from 'react-native';
130
+
131
+ // Replace these with your actual values from the Repliqo dashboard
132
+ const REPLIQO_API_KEY = 'your-api-key-here';
133
+ const REPLIQO_APP_ID = 'your-app-id';
134
+
135
+ // Use localhost for development, production URL for release builds
136
+ const REPLIQO_API_URL = __DEV__
137
+ ? 'http://10.0.2.2:3050' // Android emulator localhost
138
+ : 'https://api-repliqo.odmservice.site';
139
+
140
+ // Guard against double-initialization
141
+ let initialized = false;
142
+
143
+ export async function initRepliqo(): Promise<void> {
144
+ if (initialized) return;
145
+
146
+ try {
147
+ // 1. Initialize the SDK with your configuration
148
+ AppAnalytics.init({
149
+ apiKey: REPLIQO_API_KEY,
150
+ appId: REPLIQO_APP_ID,
151
+ baseUrl: REPLIQO_API_URL,
152
+ enableTouchTracking: false, // Disable if you don't need heatmaps
153
+ enableNavigationTracking: true, // Automatic navigation events
154
+ enableCrashTracking: true, // Capture JS errors and unhandled rejections
155
+ enableSnapshots: false, // Disable for lower overhead
156
+ batchSize: 20, // Send events in groups of 20
157
+ flushInterval: 30000, // Flush every 30 seconds
158
+ debug: __DEV__, // Log SDK activity in development
159
+ });
160
+
161
+ // 2. Gather device information
162
+ const { width, height } = Dimensions.get('window');
163
+ const deviceId = `${Platform.OS}-${Math.random().toString(36).slice(2, 10)}`;
164
+
165
+ // 3. Start a session -- this registers the device with the backend
166
+ await AppAnalytics.getInstance().startSession(deviceId, {
167
+ os: Platform.OS,
168
+ osVersion: String(Platform.Version),
169
+ model: 'Unknown', // Use react-native-device-info for real values
170
+ brand: 'Unknown',
171
+ screenWidth: width,
172
+ screenHeight: height,
173
+ appVersion: '1.0.0', // Replace with your app version
174
+ });
175
+
176
+ initialized = true;
177
+ if (__DEV__) console.log('[Repliqo] Session started');
178
+ } catch (error) {
179
+ // Never let analytics break the host app
180
+ if (__DEV__) console.warn('[Repliqo] Init failed:', error);
181
+ }
182
+ }
183
+ ```
184
+
185
+ ### Step 2: Call It from App.tsx
186
+
187
+ In your root `App.tsx`, import and call `initRepliqo()` inside a `useEffect`. Use a `setTimeout` to ensure analytics initialization never blocks your app's startup:
188
+
189
+ ```typescript
190
+ // App.tsx
191
+
192
+ import React, { useEffect } from 'react';
193
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
194
+ import { AppNavigator } from './src/navigation/AppNavigator';
195
+ import { initRepliqo } from './src/services/repliqo';
196
+
197
+ function App() {
198
+ useEffect(() => {
199
+ // Initialize analytics after a short delay so it never blocks app startup
200
+ setTimeout(initRepliqo, 3000);
201
+ }, []);
202
+
203
+ return (
204
+ <SafeAreaProvider>
205
+ <AppNavigator />
206
+ </SafeAreaProvider>
207
+ );
208
+ }
209
+
210
+ export default App;
211
+ ```
212
+
213
+ That is it. With these two files in place, the SDK will:
214
+
215
+ - Start a session with device metadata.
216
+ - Automatically flush events when the app goes to the background.
217
+ - Track navigation events and screen visits once you wire up the navigation tracker (see [Section 6](#6-navigation-tracking-automatic)).
218
+
219
+ ---
220
+
221
+ ## 5. Configuration Reference
222
+
223
+ All configuration is passed to `AppAnalytics.init()` via an `SDKConfig` object. The three required fields have no defaults and must always be provided. All other fields are optional and have sensible defaults.
224
+
225
+ | Property | Type | Default | Required | Description |
226
+ |---|---|---|---|---|
227
+ | `apiKey` | `string` | -- | Yes | Your Repliqo API key. Obtained from the dashboard under Settings > Applications. |
228
+ | `appId` | `string` | -- | Yes | Your application identifier. Must match the app ID registered in the dashboard. |
229
+ | `baseUrl` | `string` | -- | Yes | The Repliqo API base URL. Use `https://api-repliqo.odmservice.site` for production, or a local URL like `http://10.0.2.2:3050` for Android emulator development. |
230
+ | `batchSize` | `number` | `20` | No | Number of events to accumulate before sending a batch to the server. Lower values mean more frequent network requests; higher values reduce request count but increase the risk of data loss if the app is killed. |
231
+ | `flushInterval` | `number` | `30000` | No | Time in milliseconds between automatic batch flushes. Events are sent at least this often, even if `batchSize` has not been reached. Default is 30 seconds. |
232
+ | `enableTouchTracking` | `boolean` | `true` | No | When `true`, touch events (taps) are captured and sent to the backend for heatmap generation. Set to `false` if you only need navigation analytics. |
233
+ | `enableNavigationTracking` | `boolean` | `true` | No | When `true`, navigation events (screen transitions) are tracked. Works with `useNavigationTracker()` or `trackScreenChange()`. |
234
+ | `enableSnapshots` | `boolean` | `true` | No | When `true`, the SDK periodically captures a serialized snapshot of the component tree. Useful for session replay. Increases memory and bandwidth usage. |
235
+ | `enableCrashTracking` | `boolean` | `true` | No | When `true`, the SDK installs a global error handler and captures unhandled JavaScript errors and Promise rejections. Crash reports are sent immediately, not batched. |
236
+ | `snapshotInterval` | `number` | `1000` | No | Time in milliseconds between automatic snapshot captures. Only relevant when `enableSnapshots` is `true`. Default is 1 second. |
237
+ | `maxSnapshotsPerSession` | `number` | `3600` | No | Maximum number of snapshots the SDK will capture per session. Prevents runaway memory usage in long-lived sessions. At the default of 1 snapshot/second, this allows 1 hour of capture. |
238
+ | `debug` | `boolean` | `false` | No | When `true`, the SDK logs detailed information to the console (initialization, event tracking, flushes, errors). Recommended to set `debug: __DEV__` so logs only appear during development. |
239
+
240
+ ---
241
+
242
+ ## 6. Navigation Tracking (Automatic)
243
+
244
+ The SDK provides two approaches for automatic navigation tracking with `@react-navigation/native`. Choose the one that best fits your app's architecture.
245
+
246
+ ### Approach A: Using the `useNavigationTracker()` Hook
247
+
248
+ The `useNavigationTracker()` hook is the cleanest integration method. It returns a `navigationRef`, an `onReady` callback, and an `onStateChange` callback that you pass directly to `NavigationContainer`.
249
+
250
+ ```typescript
251
+ // src/navigation/AppNavigator.tsx
252
+
253
+ import React from 'react';
254
+ import { NavigationContainer } from '@react-navigation/native';
255
+ import { createStackNavigator } from '@react-navigation/stack';
256
+ import { useNavigationTracker } from '@repliqo/sdk-react-native';
257
+ import { HomeScreen } from '../screens/HomeScreen';
258
+ import { ProfileScreen } from '../screens/ProfileScreen';
259
+ import { SettingsScreen } from '../screens/SettingsScreen';
260
+
261
+ const Stack = createStackNavigator();
262
+
263
+ export function AppNavigator() {
264
+ // The hook manages all navigation tracking state internally
265
+ const { navigationRef, onReady, onStateChange } = useNavigationTracker();
266
+
267
+ return (
268
+ <NavigationContainer
269
+ ref={navigationRef}
270
+ onReady={onReady}
271
+ onStateChange={onStateChange}
272
+ >
273
+ <Stack.Navigator screenOptions={{ headerShown: false }}>
274
+ <Stack.Screen name="Home" component={HomeScreen} />
275
+ <Stack.Screen name="Profile" component={ProfileScreen} />
276
+ <Stack.Screen name="Settings" component={SettingsScreen} />
277
+ </Stack.Navigator>
278
+ </NavigationContainer>
279
+ );
280
+ }
281
+ ```
282
+
283
+ When the navigator is ready, the hook records the initial screen. On every subsequent state change, it automatically:
284
+
285
+ 1. Detects the currently active route (including nested navigators).
286
+ 2. Calls `trackNavigation(fromScreen, toScreen)` to log the transition event.
287
+ 3. Calls `onScreenExit(fromScreen)` to record how long the user spent on the previous screen.
288
+ 4. Calls `onScreenEnter(toScreen)` to start the timer for the new screen.
289
+
290
+ ### Approach B: Using `trackScreenChange()` Manually
291
+
292
+ If you need more control over how navigation state is read (for example, with complex nested navigators or non-standard setups), use the `trackScreenChange()` function directly:
293
+
294
+ ```typescript
295
+ // src/navigation/AppNavigator.tsx
296
+
297
+ import React, { useRef, useCallback } from 'react';
298
+ import { NavigationContainer } from '@react-navigation/native';
299
+ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
300
+ import { createStackNavigator } from '@react-navigation/stack';
301
+ import { trackScreenChange } from '@repliqo/sdk-react-native';
302
+ import { HomeScreen } from '../screens/HomeScreen';
303
+ import { ProfileScreen } from '../screens/ProfileScreen';
304
+
305
+ const Tab = createBottomTabNavigator();
306
+ const Stack = createStackNavigator();
307
+
308
+ // Helper to dig through nested navigators and find the active route name
309
+ function getActiveRouteName(state: any): string {
310
+ const route = state.routes[state.index];
311
+ if (route.state) return getActiveRouteName(route.state);
312
+ return route.name;
313
+ }
314
+
315
+ export function AppNavigator() {
316
+ // Keep track of the current route name between renders
317
+ const currentRoute = useRef<string>('');
318
+
319
+ const onReady = useCallback(() => {
320
+ currentRoute.current = 'Home';
321
+ // Track the initial screen (fromScreen is empty on first load)
322
+ trackScreenChange('', 'Home');
323
+ }, []);
324
+
325
+ return (
326
+ <NavigationContainer
327
+ onReady={onReady}
328
+ onStateChange={(state) => {
329
+ if (!state) return;
330
+
331
+ const newRoute = getActiveRouteName(state);
332
+ if (newRoute !== currentRoute.current) {
333
+ // This single call handles navigation event + screen enter/exit
334
+ trackScreenChange(currentRoute.current, newRoute);
335
+ currentRoute.current = newRoute;
336
+ }
337
+ }}
338
+ >
339
+ <Tab.Navigator screenOptions={{ headerShown: false }}>
340
+ <Tab.Screen name="Home" component={HomeScreen} />
341
+ <Tab.Screen name="Profile" component={ProfileScreen} />
342
+ </Tab.Navigator>
343
+ </NavigationContainer>
344
+ );
345
+ }
346
+ ```
347
+
348
+ ### What Data Gets Captured
349
+
350
+ Each navigation event produces an `AnalyticsEvent` with this structure:
351
+
352
+ ```typescript
353
+ {
354
+ type: 'navigation',
355
+ screenName: 'ProfileScreen', // The destination screen
356
+ data: {
357
+ fromScreen: 'HomeScreen', // Where the user came from
358
+ toScreen: 'ProfileScreen', // Where the user went
359
+ },
360
+ timestamp: '2026-03-25T14:30:00.000Z', // ISO 8601 timestamp
361
+ }
362
+ ```
363
+
364
+ Additionally, screen visits are tracked automatically with enter/exit timestamps and duration:
365
+
366
+ ```typescript
367
+ {
368
+ sessionId: 'abc123',
369
+ screenName: 'HomeScreen',
370
+ enteredAt: '2026-03-25T14:28:00.000Z',
371
+ exitedAt: '2026-03-25T14:30:00.000Z',
372
+ duration: 120000, // Duration in milliseconds
373
+ }
374
+ ```
375
+
376
+ ---
377
+
378
+ ## 7. Custom Events
379
+
380
+ Custom events let you track specific user actions that are meaningful to your business logic -- button clicks, form submissions, feature usage, purchase completions, and anything else you want to measure.
381
+
382
+ ### Basic Usage
383
+
384
+ ```typescript
385
+ import { AppAnalytics } from '@repliqo/sdk-react-native';
386
+
387
+ // Track a simple event (name only)
388
+ AppAnalytics.getInstance().trackCustomEvent('sign_up_completed');
389
+
390
+ // Track an event with additional data
391
+ AppAnalytics.getInstance().trackCustomEvent('item_purchased', {
392
+ itemId: 'premium-plan',
393
+ price: 9.99,
394
+ currency: 'USD',
395
+ });
396
+
397
+ // Track feature usage
398
+ AppAnalytics.getInstance().trackCustomEvent('dark_mode_toggled', {
399
+ enabled: true,
400
+ });
401
+ ```
402
+
403
+ ### Real-World Example: Tracking Button Clicks
404
+
405
+ ```typescript
406
+ // src/screens/HomeScreen.tsx
407
+
408
+ import React from 'react';
409
+ import { View, TouchableOpacity, Text } from 'react-native';
410
+ import { AppAnalytics } from '@repliqo/sdk-react-native';
411
+
412
+ export function HomeScreen() {
413
+ const handleCreateHabit = () => {
414
+ // Track the button click before performing the action
415
+ AppAnalytics.getInstance().trackCustomEvent('create_habit_tapped', {
416
+ source: 'home_screen',
417
+ });
418
+
419
+ // ... your actual logic to create a habit
420
+ };
421
+
422
+ const handleShareApp = () => {
423
+ AppAnalytics.getInstance().trackCustomEvent('share_app_tapped', {
424
+ source: 'home_screen',
425
+ method: 'native_share',
426
+ });
427
+
428
+ // ... your share logic
429
+ };
430
+
431
+ return (
432
+ <View>
433
+ <TouchableOpacity onPress={handleCreateHabit}>
434
+ <Text>Create Habit</Text>
435
+ </TouchableOpacity>
436
+ <TouchableOpacity onPress={handleShareApp}>
437
+ <Text>Share App</Text>
438
+ </TouchableOpacity>
439
+ </View>
440
+ );
441
+ }
442
+ ```
443
+
444
+ ### Best Practices for Event Naming
445
+
446
+ - Use **snake_case** for event names: `purchase_completed`, not `PurchaseCompleted` or `purchase-completed`.
447
+ - Start with a **noun** followed by a **verb in past tense**: `habit_created`, `profile_updated`, `session_started`.
448
+ - Keep names **short but descriptive**: `checkout_started` is better than `user_clicked_checkout_button`.
449
+ - Use the `data` parameter for **variable information**: pass `itemId`, `price`, `category` as data, not as part of the event name.
450
+ - Avoid **high-cardinality event names**: do not include IDs or dynamic values in the event name itself. Use `item_viewed` with `{ itemId: '123' }`, not `item_123_viewed`.
451
+
452
+ ---
453
+
454
+ ## 8. Error/Crash Tracking
455
+
456
+ The SDK can automatically capture JavaScript errors, fatal native crashes, and unhandled Promise rejections, and send them to the Repliqo backend for analysis.
457
+
458
+ ### Automatic Crash Capture
459
+
460
+ When `enableCrashTracking` is set to `true` (the default), the SDK installs:
461
+
462
+ 1. **A global error handler** via React Native's `ErrorUtils`. This captures all uncaught JavaScript errors, including fatal errors that would crash the app.
463
+ 2. **An unhandled rejection handler** that captures Promise rejections that are not caught by a `.catch()` block.
464
+
465
+ Crash reports are sent **immediately** when they occur (not batched), because the app may be about to terminate. The original React Native error handler is preserved and still called, so the standard red screen / crash behavior is unaffected.
466
+
467
+ No additional setup is needed beyond enabling the flag:
468
+
469
+ ```typescript
470
+ AppAnalytics.init({
471
+ apiKey: 'your-api-key',
472
+ appId: 'your-app-id',
473
+ baseUrl: 'https://api-repliqo.odmservice.site',
474
+ enableCrashTracking: true, // This is the default
475
+ });
476
+ ```
477
+
478
+ ### Manual Error Reporting
479
+
480
+ For errors that you catch yourself (e.g., in try/catch blocks, error boundaries, or API call failures), use `reportError()`:
481
+
482
+ ```typescript
483
+ import { AppAnalytics } from '@repliqo/sdk-react-native';
484
+
485
+ // Report a caught error with context metadata
486
+ async function fetchUserProfile(userId: string) {
487
+ try {
488
+ const response = await fetch(`/api/users/${userId}`);
489
+ if (!response.ok) {
490
+ throw new Error(`HTTP ${response.status}: Failed to fetch profile`);
491
+ }
492
+ return await response.json();
493
+ } catch (error) {
494
+ // Report to Repliqo with extra context
495
+ AppAnalytics.getInstance().reportError(error as Error, {
496
+ userId,
497
+ action: 'fetch_user_profile',
498
+ httpStatus: (error as any).status,
499
+ });
500
+
501
+ // Handle the error in the UI as normal
502
+ throw error;
503
+ }
504
+ }
505
+ ```
506
+
507
+ ### React Error Boundary Example
508
+
509
+ ```typescript
510
+ // src/components/AnalyticsErrorBoundary.tsx
511
+
512
+ import React, { Component, ErrorInfo, ReactNode } from 'react';
513
+ import { View, Text } from 'react-native';
514
+ import { AppAnalytics } from '@repliqo/sdk-react-native';
515
+
516
+ interface Props {
517
+ children: ReactNode;
518
+ fallback?: ReactNode;
519
+ }
520
+
521
+ interface State {
522
+ hasError: boolean;
523
+ }
524
+
525
+ export class AnalyticsErrorBoundary extends Component<Props, State> {
526
+ state: State = { hasError: false };
527
+
528
+ static getDerivedStateFromError(): State {
529
+ return { hasError: true };
530
+ }
531
+
532
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
533
+ // Report the error to Repliqo with component stack information
534
+ try {
535
+ AppAnalytics.getInstance().reportError(error, {
536
+ componentStack: errorInfo.componentStack,
537
+ source: 'error_boundary',
538
+ });
539
+ } catch {
540
+ // SDK may not be initialized yet
541
+ }
542
+ }
543
+
544
+ render() {
545
+ if (this.state.hasError) {
546
+ return this.props.fallback || (
547
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
548
+ <Text>Something went wrong.</Text>
549
+ </View>
550
+ );
551
+ }
552
+ return this.props.children;
553
+ }
554
+ }
555
+ ```
556
+
557
+ ### What Data Is Captured in a Crash Report
558
+
559
+ Each crash report contains the following fields:
560
+
561
+ | Field | Type | Description |
562
+ |---|---|---|
563
+ | `sessionId` | `string` or `undefined` | The active session ID, if a session is running. |
564
+ | `type` | `'js_error'` \| `'native_crash'` \| `'unhandled_rejection'` | The category of error. Fatal errors from `ErrorUtils` are reported as `native_crash`. |
565
+ | `message` | `string` | The error message (e.g., `"Cannot read property 'name' of undefined"`). |
566
+ | `stackTrace` | `string` or `undefined` | The JavaScript stack trace, if available. |
567
+ | `screenName` | `string` or `undefined` | The screen the user was on when the crash occurred. |
568
+ | `deviceInfo` | `Record<string, any>` or `undefined` | Optional device context. |
569
+ | `metadata` | `Record<string, any>` or `undefined` | Any additional metadata you pass to `reportError()`. For automatic captures, this includes `{ isFatal: boolean }`. |
570
+ | `timestamp` | `string` | ISO 8601 timestamp of when the error occurred. |
571
+
572
+ ---
573
+
574
+ ## 9. Manual Screen Tracking
575
+
576
+ In most cases, the automatic navigation tracker (Section 6) handles screen tracking for you. However, there are scenarios where you need manual control:
577
+
578
+ - Screens not managed by React Navigation (e.g., modals, bottom sheets, onboarding flows).
579
+ - Custom navigation systems that do not use `@react-navigation/native`.
580
+ - Conditional screens that appear within a single navigator route.
581
+
582
+ ### Using `trackScreenEnter()` and `trackScreenExit()`
583
+
584
+ ```typescript
585
+ import { trackScreenEnter, trackScreenExit } from '@repliqo/sdk-react-native';
586
+
587
+ // Call when the screen becomes visible
588
+ trackScreenEnter('OnboardingStep1');
589
+
590
+ // Call when the screen is no longer visible
591
+ trackScreenExit('OnboardingStep1');
592
+ ```
593
+
594
+ ### Example: Tracking a Modal Screen
595
+
596
+ ```typescript
597
+ // src/components/PremiumModal.tsx
598
+
599
+ import React, { useEffect } from 'react';
600
+ import { Modal, View, Text } from 'react-native';
601
+ import { trackScreenEnter, trackScreenExit } from '@repliqo/sdk-react-native';
602
+
603
+ interface PremiumModalProps {
604
+ visible: boolean;
605
+ onClose: () => void;
606
+ }
607
+
608
+ export function PremiumModal({ visible, onClose }: PremiumModalProps) {
609
+ useEffect(() => {
610
+ if (visible) {
611
+ // Register this modal as a screen visit when it opens
612
+ trackScreenEnter('PremiumUpgradeModal');
613
+ } else {
614
+ // Record the exit (and duration) when it closes
615
+ trackScreenExit('PremiumUpgradeModal');
616
+ }
617
+ }, [visible]);
618
+
619
+ return (
620
+ <Modal visible={visible} onRequestClose={onClose}>
621
+ <View>
622
+ <Text>Upgrade to Premium</Text>
623
+ {/* ... modal content ... */}
624
+ </View>
625
+ </Modal>
626
+ );
627
+ }
628
+ ```
629
+
630
+ ### When to Use Manual vs. Automatic
631
+
632
+ | Scenario | Recommended Approach |
633
+ |---|---|
634
+ | Standard React Navigation stack/tab screens | Automatic (`useNavigationTracker` or `trackScreenChange`) |
635
+ | Modals and bottom sheets | Manual (`trackScreenEnter` / `trackScreenExit`) |
636
+ | Onboarding wizard steps within a single route | Manual |
637
+ | Custom navigation (no React Navigation) | Manual |
638
+ | WebView content within a native screen | Manual (track the WebView "page" as a screen) |
639
+
640
+ ---
641
+
642
+ ## 10. Session Management
643
+
644
+ A session represents a single continuous usage period of your app by a user. Sessions tie all events, screen visits, and crash reports together.
645
+
646
+ ### How Sessions Work
647
+
648
+ 1. **Start**: You call `startSession()` with a device identifier and optional device info. The backend creates a session record and returns a session ID.
649
+ 2. **Track**: While the session is active, all events (navigation, touch, custom, screen visits) are associated with this session ID.
650
+ 3. **Flush**: Events are periodically flushed to the backend in batches. An automatic flush also happens when the app goes to the background.
651
+ 4. **End**: You call `endSession()` (or let the SDK handle it on destroy). This flushes remaining events and tells the backend the session is complete.
652
+
653
+ ### Starting a Session
654
+
655
+ ```typescript
656
+ import { AppAnalytics } from '@repliqo/sdk-react-native';
657
+ import { Platform, Dimensions } from 'react-native';
658
+
659
+ const analytics = AppAnalytics.getInstance();
660
+
661
+ // Generate or retrieve a persistent device ID
662
+ const deviceId = 'unique-device-identifier';
663
+
664
+ // Gather device info (see Section 11 for a complete example)
665
+ const { width, height } = Dimensions.get('window');
666
+ const deviceInfo = {
667
+ os: Platform.OS,
668
+ osVersion: String(Platform.Version),
669
+ model: 'Pixel 7',
670
+ brand: 'Google',
671
+ screenWidth: width,
672
+ screenHeight: height,
673
+ appVersion: '2.1.0',
674
+ };
675
+
676
+ // startSession returns the session ID assigned by the backend
677
+ const sessionId = await analytics.startSession(deviceId, deviceInfo);
678
+ console.log('Session ID:', sessionId);
679
+ ```
680
+
681
+ ### Ending a Session
682
+
683
+ ```typescript
684
+ // End the session -- this flushes all pending events first
685
+ await AppAnalytics.getInstance().endSession();
686
+ ```
687
+
688
+ Ending a session:
689
+
690
+ 1. Stops snapshot capture (if enabled).
691
+ 2. Records the exit for the current screen (if the user is on one).
692
+ 3. Flushes all queued events, screen visits, and snapshots.
693
+ 4. Notifies the backend that the session has ended.
694
+ 5. Clears the session ID and current screen state.
695
+
696
+ ### Checking Session State
697
+
698
+ ```typescript
699
+ const analytics = AppAnalytics.getInstance();
700
+
701
+ // Get the current session ID (null if no session is active)
702
+ const sessionId = analytics.getSessionId();
703
+
704
+ // Get the name of the screen the user is currently on
705
+ const currentScreen = analytics.getCurrentScreen();
706
+ ```
707
+
708
+ ### Session Lifecycle Diagram
709
+
710
+ ```
711
+ App Launch
712
+ |
713
+ v
714
+ AppAnalytics.init(config) --> SDK initialized, queues created
715
+ |
716
+ v
717
+ analytics.startSession() --> Backend creates session, returns ID
718
+ |
719
+ v
720
+ [User interacts with app] --> Events buffered in memory
721
+ |
722
+ v
723
+ [Batch threshold reached] --> Events sent to backend
724
+ | (automatic, every flushInterval or batchSize)
725
+ v
726
+ [App goes to background] --> Automatic flush triggered
727
+ |
728
+ v
729
+ [App returns to foreground] --> Tracking resumes (same session)
730
+ |
731
+ v
732
+ analytics.endSession() --> Final flush, session closed on backend
733
+ |
734
+ v
735
+ AppAnalytics.destroy() --> Cleanup, listeners removed
736
+ ```
737
+
738
+ ---
739
+
740
+ ## 11. Device Info
741
+
742
+ The `DeviceInfo` object provides context about the user's device with each session. This data appears in the dashboard alongside session details and crash reports.
743
+
744
+ ### Basic Device Info (No Extra Dependencies)
745
+
746
+ Using only React Native's built-in APIs:
747
+
748
+ ```typescript
749
+ import { Platform, Dimensions } from 'react-native';
750
+ import type { DeviceInfo } from '@repliqo/sdk-react-native';
751
+
752
+ function getBasicDeviceInfo(): DeviceInfo {
753
+ const { width, height } = Dimensions.get('window');
754
+
755
+ return {
756
+ os: Platform.OS, // 'ios' or 'android'
757
+ osVersion: String(Platform.Version), // e.g., '33' (Android) or '17.2' (iOS)
758
+ model: 'Unknown', // Not available without a library
759
+ brand: 'Unknown', // Not available without a library
760
+ screenWidth: width, // e.g., 393
761
+ screenHeight: height, // e.g., 852
762
+ appVersion: '1.0.0', // Hardcode or read from your config
763
+ };
764
+ }
765
+ ```
766
+
767
+ ### Enhanced Device Info with `react-native-device-info`
768
+
769
+ For richer device data, install [react-native-device-info](https://github.com/react-native-device-info/react-native-device-info):
770
+
771
+ ```bash
772
+ npm install react-native-device-info
773
+ ```
774
+
775
+ Then use it to populate the fields:
776
+
777
+ ```typescript
778
+ import { Platform, Dimensions } from 'react-native';
779
+ import DeviceInfoLib from 'react-native-device-info';
780
+ import type { DeviceInfo } from '@repliqo/sdk-react-native';
781
+
782
+ async function getEnhancedDeviceInfo(): Promise<DeviceInfo> {
783
+ const { width, height } = Dimensions.get('window');
784
+
785
+ return {
786
+ os: Platform.OS,
787
+ osVersion: String(Platform.Version),
788
+ model: DeviceInfoLib.getModel(), // e.g., 'Pixel 7'
789
+ brand: DeviceInfoLib.getBrand(), // e.g., 'Google'
790
+ screenWidth: width,
791
+ screenHeight: height,
792
+ appVersion: DeviceInfoLib.getVersion(), // e.g., '2.1.0'
793
+ };
794
+ }
795
+ ```
796
+
797
+ ### Generating a Persistent Device ID
798
+
799
+ For the `deviceId` parameter in `startSession()`, you need a value that is unique per device and persists across app launches. Here are some approaches:
800
+
801
+ ```typescript
802
+ import DeviceInfoLib from 'react-native-device-info';
803
+
804
+ // Option 1: Use react-native-device-info's unique ID (recommended)
805
+ const deviceId = await DeviceInfoLib.getUniqueId();
806
+
807
+ // Option 2: Generate a random ID and store it with AsyncStorage
808
+ import AsyncStorage from '@react-native-async-storage/async-storage';
809
+
810
+ async function getOrCreateDeviceId(): Promise<string> {
811
+ let deviceId = await AsyncStorage.getItem('repliqo_device_id');
812
+ if (!deviceId) {
813
+ deviceId = `${Platform.OS}-${Math.random().toString(36).slice(2, 10)}`;
814
+ await AsyncStorage.setItem('repliqo_device_id', deviceId);
815
+ }
816
+ return deviceId;
817
+ }
818
+ ```
819
+
820
+ ---
821
+
822
+ ## 12. Advanced: Flush Control
823
+
824
+ The SDK batches events in memory and sends them to the backend periodically. You can control when flushes happen.
825
+
826
+ ### Automatic Flushing
827
+
828
+ By default, the SDK flushes automatically in two situations:
829
+
830
+ 1. **Timer-based**: Every `flushInterval` milliseconds (default: 30,000ms), all queued events are sent.
831
+ 2. **App background**: When the app transitions to the `background` or `inactive` state, a flush is triggered immediately. This is handled internally by the SDK via an `AppState` listener and requires no action from you.
832
+
833
+ ### Manual Flushing
834
+
835
+ You can trigger a flush at any time:
836
+
837
+ ```typescript
838
+ import { AppAnalytics } from '@repliqo/sdk-react-native';
839
+
840
+ // Flush all pending events, screen visits, and snapshots immediately
841
+ await AppAnalytics.getInstance().flush();
842
+ ```
843
+
844
+ This is useful in scenarios like:
845
+
846
+ - Before navigating to a payment screen (ensure all events are sent).
847
+ - After a critical user action that you want to see in the dashboard right away.
848
+ - In a "log out" flow before ending the session.
849
+
850
+ ### Tuning `batchSize` and `flushInterval`
851
+
852
+ | Use Case | Recommended `batchSize` | Recommended `flushInterval` |
853
+ |---|---|---|
854
+ | **Low-traffic app** (few screens, few events) | `10` | `60000` (60s) |
855
+ | **Standard app** | `20` (default) | `30000` (30s, default) |
856
+ | **High-traffic app** (many touch events, frequent navigation) | `50` | `15000` (15s) |
857
+ | **Real-time monitoring** (debugging, demo) | `5` | `5000` (5s) |
858
+
859
+ Lower `batchSize` and `flushInterval` values mean more frequent network requests but lower risk of data loss. Higher values reduce network usage but events take longer to appear in the dashboard.
860
+
861
+ ---
862
+
863
+ ## 13. Advanced: Debug Mode
864
+
865
+ Debug mode enables verbose console logging from the SDK, which is essential during development and integration testing.
866
+
867
+ ### Enabling Debug Mode
868
+
869
+ ```typescript
870
+ AppAnalytics.init({
871
+ apiKey: 'your-api-key',
872
+ appId: 'your-app-id',
873
+ baseUrl: 'https://api-repliqo.odmservice.site',
874
+ debug: true, // Enable verbose logging
875
+ });
876
+ ```
877
+
878
+ A common pattern is to tie debug mode to the development build:
879
+
880
+ ```typescript
881
+ debug: __DEV__, // true in development, false in production
882
+ ```
883
+
884
+ ### What Logs to Expect
885
+
886
+ With debug mode enabled, you will see console output for every SDK operation:
887
+
888
+ ```
889
+ [Repliqo] SDK initialized with config: { baseUrl: "...", appId: "myapp", batchSize: 20, ... }
890
+ [Repliqo] Session started with ID: sess_abc123def456
891
+ [Repliqo] Screen entered: HomeScreen
892
+ [Repliqo] Navigation tracked: HomeScreen -> ProfileScreen
893
+ [Repliqo] Screen exited: HomeScreen duration: 4523
894
+ [Repliqo] Screen entered: ProfileScreen
895
+ [Repliqo] Custom event tracked: settings_opened
896
+ [Repliqo] Touch tracked: { type: "touch", screenName: "ProfileScreen", data: { x: 195, y: 420 } }
897
+ [Repliqo] Flushing queues...
898
+ [Repliqo] Flush complete
899
+ [Repliqo] App going to background/inactive, flushing queues
900
+ ```
901
+
902
+ ### How to Verify Events Are Being Sent
903
+
904
+ 1. **Enable debug mode** and watch the console for "Flushing queues..." followed by "Flush complete" without errors.
905
+ 2. **Check the dashboard**: Go to [https://repliqo.odmservice.site](https://repliqo.odmservice.site) and look for your session in the Sessions list. Events should appear within a few seconds of a flush.
906
+ 3. **Monitor network requests**: Use React Native Debugger or Flipper to inspect outgoing HTTP requests to your `baseUrl`. You should see POST requests to endpoints like `/sessions`, `/events/batch`, and `/screen-visits/batch`.
907
+ 4. **Force a flush**: Call `AppAnalytics.getInstance().flush()` from a debug button in your app to send all queued events immediately.
908
+
909
+ ---
910
+
911
+ ## 14. Dashboard Guide
912
+
913
+ The Repliqo dashboard at [https://repliqo.odmservice.site](https://repliqo.odmservice.site) provides a visual interface for all the data your SDK collects. Here is a brief overview of the main sections.
914
+
915
+ ### Sessions
916
+
917
+ The sessions view lists all recorded user sessions with:
918
+
919
+ - **Device info**: OS, OS version, device model, brand, screen dimensions.
920
+ - **Duration**: How long the session lasted.
921
+ - **Screen count**: Number of screens visited during the session.
922
+ - **Event count**: Total events tracked (navigation, touch, custom).
923
+ - **Timestamps**: Start and end times.
924
+
925
+ Click on a session to see its full timeline of events.
926
+
927
+ ### Screen Visit Analytics
928
+
929
+ View aggregate data about which screens users visit most often:
930
+
931
+ - **Screen popularity**: Ranked list of screens by visit count.
932
+ - **Average duration**: How long users spend on each screen.
933
+ - **Entry/exit points**: Which screens users land on first and leave from.
934
+
935
+ ### Heatmaps
936
+
937
+ When touch tracking is enabled, the dashboard generates visual heatmaps showing where users tap on each screen:
938
+
939
+ - **Tap density**: Color-coded overlay showing hotspots of user interaction.
940
+ - **Per-screen breakdown**: View heatmaps for individual screens.
941
+ - **Session-level replay**: See the exact touch sequence for a specific session.
942
+
943
+ ### Crash Reports
944
+
945
+ The crash reports section aggregates all errors reported by the SDK:
946
+
947
+ - **Error grouping**: Similar errors are grouped together by message and stack trace.
948
+ - **Occurrence count**: How many times each error has occurred.
949
+ - **Affected sessions**: Which sessions experienced the error.
950
+ - **Stack traces**: Full JavaScript stack traces for debugging.
951
+ - **Context**: The screen the user was on, device info, and any metadata you attached.
952
+
953
+ ### Funnels
954
+
955
+ Define multi-step funnels to track conversion through specific user flows:
956
+
957
+ - Configure a sequence of screens or events.
958
+ - See drop-off rates at each step.
959
+ - Compare funnel performance across time periods.
960
+
961
+ ### Data Export
962
+
963
+ Export your analytics data for use in external tools:
964
+
965
+ - Export sessions, events, or crash reports as CSV or JSON.
966
+ - Filter by date range, app version, or device type before exporting.
967
+
968
+ ---
969
+
970
+ ## 15. API Reference
971
+
972
+ Quick reference for all public methods on the `AppAnalytics` class.
973
+
974
+ ### Static Methods
975
+
976
+ | Method | Signature | Returns | Description |
977
+ |---|---|---|---|
978
+ | `init` | `init(config: SDKConfig)` | `AppAnalytics` | Initializes the SDK with the given configuration. Returns the singleton instance. Safe to call multiple times (subsequent calls return the existing instance). |
979
+ | `getInstance` | `getInstance()` | `AppAnalytics` | Returns the singleton instance. Throws an error if `init()` has not been called. |
980
+ | `destroy` | `destroy()` | `void` | Tears down the SDK: stops error tracking and snapshot capture, stops auto-flush timers, performs a final flush (fire-and-forget), removes the AppState listener, and nullifies the singleton. |
981
+
982
+ ### Instance Methods
983
+
984
+ | Method | Signature | Returns | Description |
985
+ |---|---|---|---|
986
+ | `startSession` | `startSession(deviceId: string, deviceInfo?: DeviceInfo)` | `Promise<string>` | Starts a new session with the backend. Returns the session ID. Sets up error tracker and snapshot capture for this session. |
987
+ | `endSession` | `endSession()` | `Promise<void>` | Ends the current session. Flushes all pending data, records the final screen exit, and notifies the backend. |
988
+ | `trackTouch` | `trackTouch(x: number, y: number, screenName?: string, extra?: Record<string, any>)` | `void` | Records a touch event at the given coordinates. If `screenName` is omitted, uses the current screen. Only works when `enableTouchTracking` is `true`. |
989
+ | `trackNavigation` | `trackNavigation(fromScreen: string, toScreen: string)` | `void` | Records a navigation event between two screens. Only works when `enableNavigationTracking` is `true`. |
990
+ | `trackCustomEvent` | `trackCustomEvent(eventName: string, data?: Record<string, any>)` | `void` | Records a custom event with an arbitrary name and optional data payload. |
991
+ | `reportError` | `reportError(error: Error, metadata?: Record<string, any>)` | `void` | Manually reports a caught error. Only works when `enableCrashTracking` is `true`. The error is sent immediately, not batched. |
992
+ | `onScreenEnter` | `onScreenEnter(screenName: string)` | `void` | Marks the user as having entered a screen. Starts the visit timer. Updates the error tracker's current screen context. |
993
+ | `onScreenExit` | `onScreenExit(screenName: string)` | `void` | Marks the user as having exited a screen. Records a `ScreenVisit` with the duration in the screen visit queue. |
994
+ | `captureSnapshot` | `captureSnapshot(tree: ComponentNode, screenName?: string)` | `void` | Manually captures a component tree snapshot. Only works when `enableSnapshots` is `true` and a session is active. |
995
+ | `flush` | `flush()` | `Promise<void>` | Immediately sends all queued events, screen visits, and snapshots to the backend. |
996
+ | `getSessionId` | `getSessionId()` | `string \| null` | Returns the current session ID, or `null` if no session is active. |
997
+ | `getCurrentScreen` | `getCurrentScreen()` | `string \| null` | Returns the name of the screen the user is currently on, or `null`. |
998
+ | `isInitialized` | `isInitialized()` | `boolean` | Returns `true` if the SDK instance exists and is ready. |
999
+
1000
+ ### Standalone Functions
1001
+
1002
+ | Function | Signature | Description |
1003
+ |---|---|---|
1004
+ | `useNavigationTracker` | `useNavigationTracker()` | React hook that returns `{ navigationRef, onReady, onStateChange }` for automatic navigation tracking with `NavigationContainer`. |
1005
+ | `trackScreenChange` | `trackScreenChange(fromScreen: string, toScreen: string)` | Convenience function that calls `trackNavigation`, `onScreenExit`, and `onScreenEnter` in sequence. |
1006
+ | `trackScreenEnter` | `trackScreenEnter(screenName: string)` | Standalone function to mark screen entry. Silently no-ops if SDK is not initialized. |
1007
+ | `trackScreenExit` | `trackScreenExit(screenName: string)` | Standalone function to mark screen exit. Silently no-ops if SDK is not initialized. |
1008
+
1009
+ ---
1010
+
1011
+ ## 16. Screen Capture Architecture
1012
+
1013
+ The SDK uses a **two-tier capture strategy** for session replay screenshots:
1014
+
1015
+ ### Tier 1: Native Multi-Window Capture (Android)
1016
+
1017
+ The SDK includes a native Android module (`ScreenCaptureModule`) that captures **all visible app windows** — including modals, alerts, system dialogs, keyboard overlays, and toasts.
1018
+
1019
+ **How it works:**
1020
+ - Uses `WindowManagerGlobal` (via reflection) to enumerate all root views in the process
1021
+ - Draws each visible view to a shared `Canvas` using `View.draw()`, composited in z-order
1022
+ - Scales to 390px width, compresses to JPEG at 40% quality
1023
+ - Returns base64-encoded JPEG to JavaScript
1024
+
1025
+ **No permissions required.** No `MediaProjection`, no foreground service, no system dialogs.
1026
+
1027
+ ### Tier 2: react-native-view-shot (Fallback)
1028
+
1029
+ If the native module is unavailable (iOS, or if the module fails to load), the SDK falls back to `react-native-view-shot`'s `captureScreen()`. This captures only the main React Native view — **modals and native dialogs are NOT included** in the fallback.
1030
+
1031
+ ### Capture Priority
1032
+
1033
+ ```
1034
+ captureScreenshot()
1035
+ ├─ Try: Native multi-window capture (Android)
1036
+ │ └─ Returns: full screenshot including modals/alerts
1037
+ ├─ Fallback: react-native-view-shot
1038
+ │ └─ Returns: main view only (no modals)
1039
+ └─ Last resort: null (frame dropped)
1040
+ ```
1041
+
1042
+ Each frame includes a `source` field (`'native'` or `'viewshot'`) so the replay player knows which method produced it.
1043
+
1044
+ ### iOS Support
1045
+
1046
+ Native multi-window capture is not yet implemented for iOS. iOS sessions use `react-native-view-shot` exclusively. This is a known limitation documented for future development.
1047
+
1048
+ ---
1049
+
1050
+ ## 17. Troubleshooting
1051
+
1052
+ ### "Failed to start session"
1053
+
1054
+ **Symptom**: `startSession()` throws an error or the console shows `[Repliqo] Init failed`.
1055
+
1056
+ **Possible causes and fixes**:
1057
+
1058
+ - **Wrong `baseUrl`**: Verify the URL is correct. For Android emulator, use `http://10.0.2.2:<port>` (not `localhost`). For iOS simulator, use `http://localhost:<port>`.
1059
+ - **Invalid API key**: Double-check your `apiKey` matches the one in the Repliqo dashboard.
1060
+ - **Network issue**: Ensure the device/emulator has network access. Try opening the `baseUrl` in a browser.
1061
+ - **Backend not running**: If developing locally, confirm the Repliqo API server is running.
1062
+
1063
+ ### Events Not Appearing in the Dashboard
1064
+
1065
+ **Symptom**: You see "Session started" in the logs, but events do not show up in the dashboard.
1066
+
1067
+ **Possible causes and fixes**:
1068
+
1069
+ - **Events are still queued**: The SDK buffers events and sends them in batches. Wait for the `flushInterval` (default 30s) or call `flush()` manually.
1070
+ - **`batchSize` too large**: If your `batchSize` is set very high and users don't generate that many events, the batch may never fill up before the flush interval. Reduce `batchSize` or `flushInterval`.
1071
+ - **Session not started**: Events are silently dropped if no session is active. Check that `startSession()` completed successfully.
1072
+ - **Feature disabled**: If `enableNavigationTracking` is `false`, navigation events are silently ignored. Same for `enableTouchTracking` and touch events.
1073
+
1074
+ ### Metro Can't Resolve the Module
1075
+
1076
+ **Symptom**: Error like `Unable to resolve module @repliqo/sdk-react-native`.
1077
+
1078
+ **Fix**: Clear the Metro bundler cache:
1079
+
1080
+ ```bash
1081
+ npx react-native start --reset-cache
1082
+ ```
1083
+
1084
+ If installing from a local path, make sure the path in `package.json` is correct and that the SDK has been built:
1085
+
1086
+ ```bash
1087
+ cd /path/to/sdk-react-native
1088
+ npm run build
1089
+ ```
1090
+
1091
+ ### Touch Events Not Working
1092
+
1093
+ **Symptom**: Touch tracking is enabled but no touch events appear.
1094
+
1095
+ **Possible causes and fixes**:
1096
+
1097
+ - **`enableTouchTracking: false`**: Verify this is set to `true` in your config.
1098
+ - **TouchTracker wrapper missing**: The `TouchTracker` component must wrap your app's component tree to intercept touch events. If you are not using it, touches will not be captured. Consider using manual `trackTouch()` calls as an alternative.
1099
+ - **Known limitation**: The `TouchTracker` component can sometimes interfere with gesture handlers (e.g., `react-native-gesture-handler`). If you experience issues, disable `enableTouchTracking` and use manual `trackTouch()` calls for specific interactions instead.
1100
+
1101
+ ### High Memory Usage
1102
+
1103
+ **Symptom**: The app consumes more memory than expected after integrating the SDK.
1104
+
1105
+ **Possible causes and fixes**:
1106
+
1107
+ - **Snapshots enabled**: Snapshot capture serializes the component tree at regular intervals. This can be memory-intensive. Disable snapshots (`enableSnapshots: false`) if you do not need session replay.
1108
+ - **Large `batchSize`**: A very large batch size means more events are held in memory before being sent. Reduce `batchSize` to flush more frequently.
1109
+ - **Long sessions**: The `maxSnapshotsPerSession` limit (default 3600) prevents unbounded snapshot growth, but screen visits and events still accumulate. Consider calling `flush()` periodically for very long sessions.
1110
+
1111
+ ### Crashes Not Being Reported
1112
+
1113
+ **Symptom**: `enableCrashTracking` is `true`, but no crash reports appear in the dashboard.
1114
+
1115
+ **Possible causes and fixes**:
1116
+
1117
+ - **Session not started**: Crash reports include the session ID for context, but they are sent even without a session. Check that the SDK is initialized (not just the session).
1118
+ - **Network failure on crash**: If the app crashes before the crash report HTTP request completes, the report may be lost. This is inherent to crash reporting in JavaScript -- native crash reporting libraries handle this better.
1119
+ - **Error caught elsewhere**: If another error handler (e.g., Sentry, Bugsnag) catches the error first and does not rethrow it, Repliqo may not see it. The SDK preserves the original error handler chain, so this should not normally be an issue.
1120
+
1121
+ ---
1122
+
1123
+ ## 17. TypeScript Types
1124
+
1125
+ All types are exported from the main package entry point. You can import them with:
1126
+
1127
+ ```typescript
1128
+ import type {
1129
+ EventType,
1130
+ AnalyticsEvent,
1131
+ TouchEventData,
1132
+ NavigationEventData,
1133
+ ScreenVisit,
1134
+ DeviceInfo,
1135
+ SDKConfig,
1136
+ ComponentNode,
1137
+ SnapshotData,
1138
+ SnapshotDiff,
1139
+ DiffPatch,
1140
+ SnapshotPayload,
1141
+ CrashReport,
1142
+ ResolvedConfig,
1143
+ SnapshotCaptureConfig,
1144
+ } from '@repliqo/sdk-react-native';
1145
+ ```
1146
+
1147
+ ### `EventType`
1148
+
1149
+ ```typescript
1150
+ type EventType = 'touch' | 'scroll' | 'gesture' | 'navigation' | 'custom';
1151
+ ```
1152
+
1153
+ ### `AnalyticsEvent`
1154
+
1155
+ ```typescript
1156
+ interface AnalyticsEvent {
1157
+ type: EventType;
1158
+ screenName?: string;
1159
+ data: Record<string, any>;
1160
+ timestamp: string; // ISO 8601
1161
+ }
1162
+ ```
1163
+
1164
+ ### `TouchEventData`
1165
+
1166
+ ```typescript
1167
+ interface TouchEventData {
1168
+ x: number;
1169
+ y: number;
1170
+ elementTag?: string;
1171
+ elementText?: string;
1172
+ }
1173
+ ```
1174
+
1175
+ ### `NavigationEventData`
1176
+
1177
+ ```typescript
1178
+ interface NavigationEventData {
1179
+ fromScreen: string;
1180
+ toScreen: string;
1181
+ }
1182
+ ```
1183
+
1184
+ ### `ScreenVisit`
1185
+
1186
+ ```typescript
1187
+ interface ScreenVisit {
1188
+ sessionId: string;
1189
+ screenName: string;
1190
+ enteredAt: string; // ISO 8601
1191
+ exitedAt?: string; // ISO 8601
1192
+ duration?: number; // Milliseconds
1193
+ }
1194
+ ```
1195
+
1196
+ ### `DeviceInfo`
1197
+
1198
+ ```typescript
1199
+ interface DeviceInfo {
1200
+ os: string;
1201
+ osVersion: string;
1202
+ model: string;
1203
+ brand: string;
1204
+ screenWidth: number;
1205
+ screenHeight: number;
1206
+ appVersion?: string;
1207
+ }
1208
+ ```
1209
+
1210
+ ### `SDKConfig`
1211
+
1212
+ ```typescript
1213
+ interface SDKConfig {
1214
+ apiKey: string;
1215
+ appId: string;
1216
+ baseUrl: string;
1217
+ batchSize?: number;
1218
+ flushInterval?: number;
1219
+ enableTouchTracking?: boolean;
1220
+ enableNavigationTracking?: boolean;
1221
+ enableSnapshots?: boolean;
1222
+ snapshotInterval?: number;
1223
+ maxSnapshotsPerSession?: number;
1224
+ enableCrashTracking?: boolean;
1225
+ debug?: boolean;
1226
+ }
1227
+ ```
1228
+
1229
+ ### `CrashReport`
1230
+
1231
+ ```typescript
1232
+ interface CrashReport {
1233
+ sessionId?: string;
1234
+ type: 'js_error' | 'native_crash' | 'unhandled_rejection';
1235
+ message: string;
1236
+ stackTrace?: string;
1237
+ screenName?: string;
1238
+ deviceInfo?: Record<string, any>;
1239
+ metadata?: Record<string, any>;
1240
+ timestamp: string; // ISO 8601
1241
+ }
1242
+ ```
1243
+
1244
+ ### `ComponentNode`
1245
+
1246
+ ```typescript
1247
+ interface ComponentNode {
1248
+ type: string;
1249
+ props: Record<string, any>;
1250
+ text?: string;
1251
+ children?: ComponentNode[];
1252
+ bounds?: {
1253
+ x: number;
1254
+ y: number;
1255
+ width: number;
1256
+ height: number;
1257
+ };
1258
+ }
1259
+ ```
1260
+
1261
+ ### `SnapshotData`
1262
+
1263
+ ```typescript
1264
+ interface SnapshotData {
1265
+ screenName: string;
1266
+ timestamp: string;
1267
+ sequence: number;
1268
+ isDiff: boolean;
1269
+ tree: ComponentNode | SnapshotDiff;
1270
+ screenDimensions: {
1271
+ width: number;
1272
+ height: number;
1273
+ };
1274
+ }
1275
+ ```
1276
+
1277
+ ### `SnapshotDiff`
1278
+
1279
+ ```typescript
1280
+ interface SnapshotDiff {
1281
+ type: 'diff';
1282
+ patches: DiffPatch[];
1283
+ }
1284
+ ```
1285
+
1286
+ ### `DiffPatch`
1287
+
1288
+ ```typescript
1289
+ interface DiffPatch {
1290
+ path: string;
1291
+ op: 'add' | 'remove' | 'replace';
1292
+ value?: any;
1293
+ oldValue?: any;
1294
+ }
1295
+ ```
1296
+
1297
+ ### `SnapshotPayload`
1298
+
1299
+ ```typescript
1300
+ interface SnapshotPayload {
1301
+ sessionId: string;
1302
+ screenName: string;
1303
+ timestamp: string;
1304
+ sequence: number;
1305
+ isDiff: boolean;
1306
+ data: Record<string, any>;
1307
+ }
1308
+ ```
1309
+
1310
+ ---
1311
+
1312
+ **Need help?** Open an issue on the Repliqo repository or reach out through the dashboard at [https://repliqo.odmservice.site](https://repliqo.odmservice.site).
1313
+
1314
+ ## 18. User Identity (identify)
1315
+
1316
+ Attach your app's own user identity to sessions — call once after login:
1317
+
1318
+ ```typescript
1319
+ import { AppAnalytics } from '@repliqo/sdk-react-native';
1320
+
1321
+ // After a successful login:
1322
+ AppAnalytics.getInstance().identify('user-42', {
1323
+ plan: 'pro',
1324
+ locale: 'es-CO',
1325
+ });
1326
+
1327
+ // On logout:
1328
+ AppAnalytics.getInstance().resetIdentity();
1329
+ ```
1330
+
1331
+ - The identity is attached to the active session immediately and
1332
+ re-attached to every future session automatically.
1333
+ - In the dashboard you can filter sessions by user:
1334
+ `GET /api/sessions?userId=user-42`.
1335
+ - Avoid sending PII you don't want stored (plain-text emails, names).
1336
+
1337
+ ## 19. Offline Persistence
1338
+
1339
+ Events buffered when the app goes to background — and crash reports whose
1340
+ network send never completed — can survive app kills:
1341
+
1342
+ ```typescript
1343
+ import AsyncStorage from '@react-native-async-storage/async-storage';
1344
+
1345
+ AppAnalytics.init({
1346
+ apiKey: 'rk_...',
1347
+ appId: 'your-app',
1348
+ storage: AsyncStorage, // enables offline persistence
1349
+ });
1350
+ ```
1351
+
1352
+ - If `storage` is omitted, the SDK tries to auto-load
1353
+ `@react-native-async-storage/async-storage`; if it isn't installed,
1354
+ persistence is silently disabled (in-memory buffering only).
1355
+ - Crash reports are persisted BEFORE the network send, so fatal crashes
1356
+ (where the app dies before the request completes) are re-sent on the
1357
+ next launch.
1358
+ - Any AsyncStorage-compatible object works (`getItem`/`setItem`/`removeItem`).
1359
+
1360
+ ## 20. RepliqoFlatList
1361
+
1362
+ `FlatList` drop-in with the same analytics behavior as `RepliqoScrollView`
1363
+ (scroll-offset tracking for heatmaps, viewport tiles for full-content
1364
+ backgrounds, fixed-vs-content touch classification):
1365
+
1366
+ ```tsx
1367
+ import { RepliqoFlatList } from '@repliqo/sdk-react-native';
1368
+
1369
+ <RepliqoFlatList
1370
+ data={items}
1371
+ keyExtractor={(item) => item.id}
1372
+ renderItem={({ item }) => <Row item={item} />}
1373
+ />
1374
+ ```
1375
+
1376
+ Works with virtualization: tiles are captured from what's on screen as
1377
+ the user naturally scrolls, exactly like the ScrollView variant.
1378
+
1379
+ > **Note (older React Native)**: the optional auto-load of AsyncStorage uses
1380
+ > a dynamic `require`. Metro treats missing optional dependencies gracefully
1381
+ > when `transformer.allowOptionalDependencies` is enabled (default in
1382
+ > `@react-native/metro-config` for RN 0.72+ and in Expo). On older RN
1383
+ > versions either install AsyncStorage, pass your own `storage`, or enable
1384
+ > that Metro option.