# Conviva React Native App Analytics - JS/TS Snippets

Only read this file when directed by AGENTS.md. All imports use `@convivainc/conviva-react-native-appanalytics`.

---

## Initialization

Always create a dedicated `src/conviva.ts` (TypeScript) or `src/conviva.js` (JavaScript) module. Call `createTracker` once at module level in this file and export the tracker instance. Then side-effect import it from the root component so the tracker is initialized before any screen renders.

**`src/conviva.ts` (TypeScript):**
```ts
import { createTracker, ReactNativeTracker } from '@convivainc/conviva-react-native-appanalytics';

let tracker: ReactNativeTracker | undefined;
try {
  tracker = createTracker('YOUR_CUSTOMER_KEY', 'YOUR_APP_NAME');
  if (!tracker) {
    console.error('Tracker initialization returned null');
  }
} catch (error) {
  console.error(error);
}
export { tracker };
```

**`src/conviva.js` (JavaScript):**
```js
import { createTracker } from '@convivainc/conviva-react-native-appanalytics';

let tracker;
try {
  tracker = createTracker('YOUR_CUSTOMER_KEY', 'YOUR_APP_NAME');
  if (!tracker) {
    console.error('Tracker initialization returned null');
  }
} catch (error) {
  console.error(error);
}
export { tracker };
```

**Root component (`App.tsx` / `App.js` / `index.js`) — side-effect import only:**
```ts
import './conviva';
```

> **YOUR_CUSTOMER_KEY** - A string to identify this specific Conviva account. Use separate keys for development/debug and production.
> **YOUR_APP_NAME** - A string value for the app name that uniquely identifies your app across platforms.

**Do NOT** call `createTracker` inside `App.tsx` or any other file. The `conviva.ts` module is the single source of truth for tracker initialization.

---

## Accessing the Tracker Across Components

Call `createTracker` exactly once — inside `src/conviva.ts` (or `src/conviva.js`). Import the exported tracker instance wherever it is needed. Never re-initialize.

**Importing via path alias (preferred when a `@src` or similar alias is configured in `babel.config.js` / `tsconfig.json`):**

```ts
import { tracker } from '@src/conviva';
try {
  if (tracker != null) {
    tracker.trackCustomEvent('my_event', { key: 'value' });
  }
} catch (error) {
  console.error(error);
}
```

**Importing via relative path (when no alias is configured):**

```ts
import { tracker } from '../conviva';
try {
  if (tracker != null) {
    tracker.trackCustomEvent('my_event', { key: 'value' });
  }
} catch (error) {
  console.error(error);
}
```

Before writing any import, check `babel.config.js` (or `.babelrc`) for a `module-resolver` plugin with an `alias` entry pointing to `src/`. Use the alias form if present; use a relative path otherwise.

**Pattern 2 - React Context (for larger apps that need dependency injection):**

```ts
// ConvivaContext.tsx
import React, { createContext, useContext } from 'react';
import { createTracker, ReactNativeTracker } from '@convivainc/conviva-react-native-appanalytics';

let tracker: ReactNativeTracker | undefined;
try {
  tracker = createTracker('YOUR_CUSTOMER_KEY', 'YOUR_APP_NAME');
  if (!tracker) {
    console.error('Tracker initialization returned null');
  }
} catch (error) {
  console.error(error);
}
const ConvivaContext = createContext<ReactNativeTracker | undefined>(tracker);

export const ConvivaProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => (
  <ConvivaContext.Provider value={tracker}>{children}</ConvivaContext.Provider>
);

export const useConviva = (): ReactNativeTracker => useContext(ConvivaContext);
```

Wrap the root component:
```tsx
// App.tsx
import { ConvivaProvider } from './ConvivaContext';

export default function App() {
  return (
    <ConvivaProvider>
      <NavigationContainer>
        {/* ... */}
      </NavigationContainer>
    </ConvivaProvider>
  );
}
```

Use in any component:
```tsx
const tracker = useConviva();
try {
  if (tracker != null) {
    tracker.trackCustomEvent('my_event', { key: 'value' });
  }
} catch (error) {
  console.error(error);
}
```

**Pattern 3 - Root component with prop drilling (only for very small apps with no Redux/Context layer):**

Initialize in `src/conviva.ts` and pass `tracker` as a prop to child screens that need it directly.

---

## iOS Native Tracker Initialization (Hybrid Apps Only)

For hybrid iOS apps with native ViewControllers, initialize the tracker natively in `AppDelegate` before the RN bridge loads. This ensures native screens are tracked from app launch.

**Objective-C (`AppDelegate.m` or `AppDelegate.mm`):**
```objc
@import Foundation;      // Required for NSException and NSLog
@import ConvivaAppAnalytics;

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    @try {
        id<CATTrackerController> tracker = [CATAppAnalytics createTrackerWithCustomerKey:@"YOUR_CUSTOMER_KEY" appName:@"YOUR_APP_NAME"];
        if (tracker == nil) {
            NSLog(@"Conviva tracker init returned nil");
        }
    } @catch (NSException *exception) {
        NSLog(@"Conviva tracker init failed: %@", exception);
    }

    // ... existing RCTBridge / RCTRootView setup ...
    return YES;
}

@end
```

**Swift (`AppDelegate.swift`):**
```swift
import ConvivaAppAnalytics

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        // Note: CATAppAnalytics.createTracker is an Objective-C method and does not throw Swift errors.
        // Use guard to safely handle a nil result.
        guard let tracker = CATAppAnalytics.createTracker(customerKey: "YOUR_CUSTOMER_KEY", appName: "YOUR_APP_NAME") else {
            return false
        }

        // ... existing RCTBridge / RCTRootView setup ...
        return true
    }
}
```

> **YOUR_CUSTOMER_KEY** and **YOUR_APP_NAME** must match the values used in the JavaScript `createTracker(...)` call in `src/conviva.ts`. The SDK deduplicates internally -- the second `createTracker` call from the JS bridge reuses the tracker already initialized natively.
>
> Use only the two-argument form. Do not pass a configuration dictionary or builder as a third argument.

---

## User ID

Place immediately after successful login or registration at the convergence point. Use the tracker returned from `createTracker`, or the same module-level instance.

**Set user ID (preferred - using setSubjectData):**
```js
try {
  if (tracker != null) {
    tracker.setSubjectData({ userId: userId });
  }
} catch (error) {
  console.error(error);
}
```

**Set user ID (alternative):**
```js
try {
  if (tracker != null) {
    tracker.setUserId(userId);
  }
} catch (error) {
  console.error(error);
}
```

**Clear on logout:**
```js
try {
  if (tracker != null) {
    tracker.setSubjectData({ userId: null });
    // or
    tracker.setUserId(null);
  }
} catch (error) {
  console.error(error);
}
```

> `userId` must be a non-PII identifier: opaque provider UID, stored UUID, or backend account ID. Never pass email, phone, full name, or any PII.

---

## Custom Events

```js
const eventName = 'your_event_name';
const eventData = {
  identifier1: intValue,
  identifier2: boolValue,
  identifier3: 'stringValue',
};
try {
  if (tracker != null) {
    tracker.trackCustomEvent(eventName, eventData);
  }
} catch (error) {
  console.error(error);
}
```

**TypeScript:**
```ts
try {
  if (tracker != null) {
    tracker.trackCustomEvent('your_event_name', {
      identifier1: 42,
      identifier2: true,
      identifier3: 'stringValue',
    });
  }
} catch (error) {
  console.error(error);
}
```

---

## Custom Tags

Custom tags are global key-value pairs applied to all subsequent events until cleared.

**Set tags:**
```js
const tagsToSet = {
  tagKey1: 'tagValue1',
  tagKey2: 100,
  tagKey3: true,
};
try {
  if (tracker != null) {
    tracker.setCustomTags(tagsToSet);
  }
} catch (error) {
  console.error(error);
}
```

**Set tags with category:**
```js
try {
  if (tracker != null) {
    tracker.setCustomTagsWithCategory('categoryName', {
      tagKey1: 'tagValue1',
      tagKey2: 100,
    });
  }
} catch (error) {
  console.error(error);
}
```

**Clear specific tags:**
```js
try {
  if (tracker != null) {
    tracker.clearCustomTags(['tagKey1', 'tagKey2']);
  }
} catch (error) {
  console.error(error);
}
```

**Clear all tags:**
```js
try {
  if (tracker != null) {
    tracker.clearAllCustomTags();
  }
} catch (error) {
  console.error(error);
}
```

---

## Revenue Event Tracking (>= 0.2.8)

Use `trackRevenueEvent()` to track purchase and revenue events. Required fields: `totalOrderAmount`, `transactionId`, `currency`.

```js
try {
  if (tracker != null) {
    tracker.trackRevenueEvent({
      totalOrderAmount: 49.99,
      transactionId: 'txn-001',
      currency: 'USD',
      taxAmount: 4.50,
      shippingCost: 5.99,
      paymentMethod: 'credit_card',
      items: [
        {
          productId: 'prod-123',
          name: 'Widget',
          unitPrice: 19.99,
          quantity: 2,
        },
      ],
    });
  }
} catch (error) {
  console.error(error);
}
```

**TypeScript:**
```ts
import { RevenueEventProps } from '@convivainc/conviva-react-native-appanalytics';

const revenueEvent: RevenueEventProps = {
  totalOrderAmount: 49.99,
  transactionId: 'txn-001',
  currency: 'USD',
};
try {
  if (tracker != null) {
    tracker.trackRevenueEvent(revenueEvent);
  }
} catch (error) {
  console.error(error);
}
```

---

## PageView Tracking

Use to track in-app page navigations explicitly (distinct from automatic screen view tracking).

```js
try {
  if (tracker != null) {
    tracker.trackPageView({
      pageUrl: 'https://example.com/page',
      pageTitle: 'Page Title',    // optional
      referrer: 'https://example.com', // optional
    });
  }
} catch (error) {
  console.error(error);
}
```

---

## Error Tracking (>= 0.5.0)

JS error tracking is **on by default** — uncaught errors and unhandled promise rejections are captured automatically once `createTracker(...)` runs.

Everything below is **optional**; add it only when the developer requests it (see AGENTS.md § 16a Step 1). `trackError` and `errorTracker` are **standalone exports** — import them directly; they do not require the `tracker` instance and need no `if (tracker != null)` guard.

### Disable or configure via `errorTracking`

Pass `errorTracking` as the third argument to `createTracker` in `src/conviva.ts` (or `src/conviva.js`). Pass `false` to disable, or an object to tune capture. Omit it entirely to keep the defaults.

#### Allowed `errorTracking` config keys

When passing the object form, use **only** these keys — do not invent others. All are optional.

| Key | Type | Default | Effect |
|---|---|---|---|
| `enabled` | `boolean` | `true` | Master switch for the error tracking module. |
| `captureGlobalErrors` | `boolean` | `true` | Capture uncaught errors via the global handler. |
| `captureUnhandledRejections` | `boolean` | `true` | Capture unhandled promise rejections. |
| `suppressInDev` | `boolean` | `false` | Skip capture while running in `__DEV__`. |
| `enableRateLimiting` | `boolean` | `true` | Drop events once the per-window limit is exceeded. |
| `maxEventsPerWindow` | `number` | `20` | Max events captured per rate-limit window. |
| `rateLimitWindowMs` | `number` | `1000` | Rate-limit window length, in milliseconds. |
| `disconnectDurationMs` | `number` | `2000` | Cool-down after the limit is hit, in milliseconds. |
| `promiseRejectionsAsHandled` | `boolean` | `false` | Report unhandled rejections as handled (`warning`) instead of unhandled (`error`). |
| `bundleId` | `string` | — | JS bundle hash for symbolication of OTA-updated apps. |
| `beforeCapture` | `(payload) => boolean \| void` | — | Enrich or filter each event; return `false` to drop it. |

> `errorTracking: false` (the boolean form) fully disables the module. The object form keeps tracking on and overrides only the keys you set.

**`src/conviva.ts` (TypeScript) — disable:**
```ts
import { createTracker, ReactNativeTracker } from '@convivainc/conviva-react-native-appanalytics';

let tracker: ReactNativeTracker | undefined;
try {
  tracker = createTracker('YOUR_CUSTOMER_KEY', 'YOUR_APP_NAME', {
    errorTracking: false, // fully disable JS error capture
  });
  if (!tracker) {
    console.error('Tracker initialization returned null');
  }
} catch (error) {
  console.error(error);
}
export { tracker };
```

**`src/conviva.js` (JavaScript) — tune capture:**
```js
import { createTracker } from '@convivainc/conviva-react-native-appanalytics';

let tracker;
try {
  tracker = createTracker('YOUR_CUSTOMER_KEY', 'YOUR_APP_NAME', {
    errorTracking: {
      suppressInDev: true,       // skip capture in __DEV__
      maxEventsPerWindow: 10,    // default 20
      beforeCapture: (payload) => {
        // Return false to drop an event; mutate payload to enrich it.
        if (payload.message.includes('Network request failed')) {
          return false;
        }
      },
    },
  });
  if (!tracker) {
    console.error('Tracker initialization returned null');
  }
} catch (error) {
  console.error(error);
}
export { tracker };
```

**Merging with an existing third argument** — `createTracker` takes a single config object. If one is already passed (e.g. `clidSyncConfig` from WebView CLID sync), add `errorTracking` to the **same** object; do not add a second argument or replace existing keys:
```js
tracker = createTracker('YOUR_CUSTOMER_KEY', 'YOUR_APP_NAME', {
  clidSyncConfig: {
    webViewCookie: { domains: ['.example.com'] },
  },
  errorTracking: {
    suppressInDev: true,
  },
});
```

### Capture render-phase errors with `ConvivaErrorBoundary`

Wrap any subtree to capture errors thrown during render and show a fallback UI.

```jsx
import { ConvivaErrorBoundary } from '@convivainc/conviva-react-native-appanalytics';
import { View, Text, Button } from 'react-native';

<ConvivaErrorBoundary
  name="CheckoutScreen"
  fallback={({ error, reset }) => (
    <View>
      <Text>Something went wrong: {String(error && error.message)}</Text>
      <Button title="Try again" onPress={reset} />
    </View>
  )}
>
  <CheckoutScreen />
</ConvivaErrorBoundary>
```

### Report handled errors with `trackError`

```js
import { trackError } from '@convivainc/conviva-react-native-appanalytics';

try {
  await riskyOperation();
} catch (error) {
  try {
    await trackError({
      message: error.message,
      errorType: error.name,   // optional
      stackTrace: error.stack, // optional
      isFatal: false,          // optional
      isHandled: true,         // optional
      attributes: { feature: 'checkout', step: 'payment' }, // optional
    });
  } catch (e) {
    console.error(e);
  }
}
```

> `trackError` is also available on the tracker instance: `tracker.trackError({ message: '...' })`. Only `message` is required.

### Attach custom attributes to every error

```js
import { errorTracker } from '@convivainc/conviva-react-native-appanalytics';

try {
  errorTracker.addAttribute('appBuild', '2026.6.1');
  errorTracker.addAttribute('environment', 'production');
  // Remove when no longer relevant:
  errorTracker.removeAttribute('environment');
} catch (error) {
  console.error(error);
}
```

> `errorTracker` also exposes `setEnabled(boolean)` to toggle capture and `setRateLimitingEnabled(boolean)` to toggle rate limiting at runtime.

---

## React Navigation Autotracking

### React Navigation >= 5 (NavigationContainer)

Import and wrap `NavigationContainer` in your root component. Pass all existing props unchanged.

**JavaScript:**
```js
import { NavigationContainer } from '@react-navigation/native';
import {
  withReactNavigationAutotrack,
  autocaptureNavigationTrack,
} from '@convivainc/conviva-react-native-appanalytics';

let ConvivaNavigationContainer;
try {
  ConvivaNavigationContainer =
    withReactNavigationAutotrack(autocaptureNavigationTrack)(NavigationContainer);
  if (!ConvivaNavigationContainer) {
    console.error('Navigation container setup returned null');
  }
} catch (error) {
  console.error(error);
}
```

**TypeScript:**
```ts
import { NavigationContainer } from '@react-navigation/native';
import {
  withReactNavigationAutotrack,
  autocaptureNavigationTrack,
} from '@convivainc/conviva-react-native-appanalytics';

let ConvivaNavigationContainer: typeof NavigationContainer | undefined;
try {
  ConvivaNavigationContainer =
    withReactNavigationAutotrack(autocaptureNavigationTrack)(NavigationContainer) as typeof NavigationContainer;
  if (!ConvivaNavigationContainer) {
    console.error('Navigation container setup returned null');
  }
} catch (error) {
  console.error(error);
}

// In your render / return:
export default function App() {
  return (
    <ConvivaNavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="Settings" component={SettingsScreen} />
      </Tab.Navigator>
    </ConvivaNavigationContainer>
  );
}
```

### React Navigation < 5 (createAppContainer)

Wrap the result of `createAppContainer()` with `withReactNavigationAutotrack(autocaptureNavigationTrack)`.

```js
import { createStackNavigator, createAppContainer } from 'react-navigation';
import {
  withReactNavigationAutotrack,
  autocaptureNavigationTrack,
} from '@convivainc/conviva-react-native-appanalytics';

const AppNavigator = createAppContainer(
  createStackNavigator(
    {
      Home: { screen: HomeScreen },
      Settings: { screen: SettingsScreen },
    },
    { initialRouteName: 'Home' }
  )
);

let App;
try {
  App = withReactNavigationAutotrack(autocaptureNavigationTrack)(AppNavigator);
  if (!App) {
    console.error('Navigation app setup returned null');
  }
} catch (error) {
  console.error(error);
}

export default App;
```

---

## Babel Plugin Configuration

The required Babel configuration depends on the installed `CONVIVA_RN_VERSION`. Check `package.json` before applying.

### For CONVIVA_RN_VERSION <= 0.2.8

Add two separate plugins for button click auto-detection and `displayName` injection.

#### babel.config.js

```js
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    // ... existing plugins ...
    './node_modules/@convivainc/conviva-react-native-appanalytics/instrumentation/index.js',
    'add-react-displayname',
  ],
};
```

#### .babelrc

```json
{
  "presets": ["module:metro-react-native-babel-preset"],
  "plugins": [
    "./node_modules/@convivainc/conviva-react-native-appanalytics/instrumentation/index.js",
    "add-react-displayname"
  ]
}
```

Add the following to `devDependencies` in `package.json`:
```json
"babel-plugin-add-react-displayname": "0.0.5",
"babel-types": "^6.26.0",
"babel-template": "^6.26.0"
```

- `babel-plugin-add-react-displayname` `0.0.5` is the final published version.
- `babel-types` and `babel-template` at `^6.26.0` are legacy Babel 6 packages required by `instrumentation/index.js`; they are not installed automatically in Babel 7 projects.

Then run `npm install` (or `yarn` / `pnpm install`) from the project root.

---

### For CONVIVA_RN_VERSION >= 0.3.0

A single unified Conviva plugin handles both button click and screen view auto-detection.

#### babel.config.js

Add the Conviva instrumentation plugin to the `plugins` array. Append only - do not modify or remove existing entries.

```js
module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    // ... existing plugins ...
    '@convivainc/conviva-react-native-appanalytics/plugin',
  ],
};
```

#### .babelrc

```json
{
  "presets": ["module:metro-react-native-babel-preset"],
  "plugins": [
    "@convivainc/conviva-react-native-appanalytics/plugin"
  ]
}
```

> The Conviva instrumentation plugin bundles `@babel/plugin-transform-react-display-name` internally via Babel's `inherits` -- do NOT add it separately to `plugins`. `displayName` injection (used for button click and screen_view component identification) is handled automatically.
> `@babel/plugin-transform-react-display-name` is declared as a direct `dependency` of the Conviva package and installs transitively into the host project's `node_modules` -- no explicit `devDependencies` entry is required.

---

After modifying `babel.config.js` (either version), restart the Metro bundler with `--reset-cache`:
```bash
npx react-native start --reset-cache
```

---

## WebView Event Bridge (Optional)

To track events from web content rendered in `react-native-webview`, set the `onMessage` callback:

```js
import { getWebViewCallback } from '@convivainc/conviva-react-native-appanalytics';
import { WebView } from 'react-native-webview';

let webViewCallback;
try {
  webViewCallback = getWebViewCallback();
  if (!webViewCallback) {
    console.error('WebView callback setup returned null');
  }
} catch (error) {
  console.error(error);
}

// In your component render:
<WebView
  source={{ uri: 'https://your-web-app.com' }}
  onMessage={webViewCallback}
/>
```

---

## Client ID (Optional)

> **Prefer automatic sync for `react-native-webview`.** If the app uses `react-native-webview` v11+, use `clidSyncConfig` in `createTracker` (see **§ WebView Client ID Sync** below) — it handles cookie seeding and JS bridge automatically with no manual code.
>
> Use `getClientId()` / `setClientId()` only for surfaces automatic sync cannot reach, or when managing the client ID for your own backend purposes.

```js
import { getClientId, setClientId } from '@convivainc/conviva-react-native-appanalytics';

try {
  // Get current client ID
  const clientId = await getClientId();
  if (clientId == null) {
    console.error('getClientId returned null');
  }

  // Set a specific client ID
  await setClientId('your-client-id');
} catch (error) {
  console.error(error);
}
```

---

## WebView Client ID Sync (Optional)

Pass `clidSyncConfig` to `createTracker` only when the developer explicitly requests linking native and in-app WebView sessions. Omit this config entirely if not requested.

**`src/conviva.ts` (TypeScript):**
```ts
import { createTracker, ReactNativeTracker } from '@convivainc/conviva-react-native-appanalytics';

let tracker: ReactNativeTracker | undefined;
try {
  tracker = createTracker('YOUR_CUSTOMER_KEY', 'YOUR_APP_NAME', {
    clidSyncConfig: {
      webViewCookie: {
        domains: ['.example.com', '.partner.com'], // leading-dot covers all subdomains
      },
    },
  });
  if (!tracker) {
    console.error('Tracker initialization returned null');
  }
} catch (error) {
  console.error(error);
}
export { tracker };
```

**`src/conviva.js` (JavaScript):**
```js
import { createTracker } from '@convivainc/conviva-react-native-appanalytics';

let tracker;
try {
  tracker = createTracker('YOUR_CUSTOMER_KEY', 'YOUR_APP_NAME', {
    clidSyncConfig: {
      webViewCookie: {
        domains: ['.example.com', '.partner.com'], // leading-dot covers all subdomains
      },
    },
  });
  if (!tracker) {
    console.error('Tracker initialization returned null');
  }
} catch (error) {
  console.error(error);
}
export { tracker };
```

> Replace `.example.com` and `.partner.com` with the actual domains hosting the in-app WebView content. Use the same domain list in Conviva remote config to ensure uninterrupted client ID sharing from the very first WebView load.
