npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sng-nexus

v1.0.12

Published

React Native client SDK for the Nexus event tracking platform. Collects events from mobile apps and sends them to the Nexus API with automatic offline queuing and retry.

Readme

sng-nexus

React Native client SDK for the Nexus event tracking platform. Collects events from mobile apps and sends them to the Nexus API with automatic offline queuing and retry.

Installation

pnpm add sng-nexus

# Peer dependencies (required)
pnpm add @react-native-async-storage/async-storage @react-native-community/netinfo

# Optional: iOS ad tracking status for Meta CAPI (not for Kids category apps)
pnpm add react-native-tracking-transparency

# Optional: iOS device model for Meta CAPI extinfo
pnpm add react-native-device-info

For iOS, run npx pod-install after installing.

Kids apps: Do not install react-native-tracking-transparency. Apple rejects Kids category apps that use ATT. The SDK will automatically send advertiser_tracking_enabled: 0 when the package is not installed.

Optional dependencies

| Package | Purpose | Fallback | |---|---|---| | react-native-tracking-transparency | iOS ATT status for advertiser_tracking_enabled | Defaults to 0 | | react-native-device-info | iOS device model (e.g., iPhone15,2), app package/version | Empty string (Android uses built-in PlatformConstants.Model) |

Google Play Install Referrer (Android)

The SDK auto-detects a native module named RNInstallReferrer to fetch UTM attribution from the Google Play Install Referrer API. No third-party npm package is needed — provide the native module via an Expo config plugin (see Expo Config Plugin below) or a custom native module. If no native module is found, the SDK silently skips referrer fetching. You can also call NexusClient.setInstallReferrer(referrerString) manually.

Quick Start

import { NexusClient, NexusEvents } from 'sng-nexus';

// Initialize once at app startup
await NexusClient.init({
  endpoint: 'https://nexus.yourserver.com',
  apiKey: 'myapp:your-secret',
  debug: __DEV__,
});

// Identify user after login
NexusClient.identify({ userId: 'u_123' });

// Track events
NexusClient.track(NexusEvents.PURCHASE, {
  value: 4.99,
  currency: 'USD',
  email: '[email protected]',
});

// Reset identity on logout
NexusClient.resetIdentity();

// Force flush before app backgrounds
await NexusClient.flush();

// Shut down (cleanup timers and listeners)
await NexusClient.shutdown();

API

NexusClient.init(config)

Initializes the SDK. Must be called before any other method. Loads any persisted events from a previous session and starts the flush timer and network listener. On first-ever launch, automatically sends an install event (used by Reddit App Install campaigns).

Config options:

| Option | Type | Default | Description | |---|---|---|---| | endpoint | string | required | Nexus server base URL (e.g., https://nexus.example.com) | | apiKey | string | required | API key in appname:secret format | | flushInterval | number | 30 | Seconds between automatic flush attempts | | maxQueueSize | number | 1000 | Max events stored in offline queue. Oldest dropped when exceeded | | maxRetries | number | 5 | Max retry attempts per event before it's dropped | | debug | boolean | false | Log SDK activity to console |

NexusClient.identify(identity)

Sets the user identity. Persisted in memory and attached to all subsequent track() calls.

NexusClient.identify({ userId: 'u_123' });
NexusClient.identify({ anonymousId: 'anon_456' });
NexusClient.identify({ userId: 'u_123', anonymousId: 'anon_456' });

NexusEvents

Standard event name constants mapped to both Meta and Reddit CAPI. Use these for type safety and correct platform mapping:

import { NexusClient, NexusEvents } from 'sng-nexus';

NexusClient.track(NexusEvents.PURCHASE, { value: 4.99, currency: 'USD' });
NexusClient.track(NexusEvents.SIGN_UP, { email: '[email protected]' });

| Constant | Value | Meta | Reddit | |---|---|---|---| | INSTALL | install | install (custom) | INSTALL | | PURCHASE | purchase | Purchase | PURCHASE | | LEAD | lead | Lead | LEAD | | SIGN_UP | sign_up | CompleteRegistration | SIGN_UP | | ADD_TO_CART | add_to_cart | AddToCart | ADD_TO_CART | | VIEW_CONTENT | view_content | ViewContent | VIEW_CONTENT | | SEARCH | search | Search | SEARCH | | ADD_TO_WISHLIST | add_to_wishlist | AddToWishlist | ADD_TO_WISHLIST | | PAGE_VISIT | page_visit | PageView | PAGE_VISIT | | GAME_ENDED | game_ended | game_ended (custom) | CUSTOM |

Custom event names are also supported — they pass through as-is to Meta and map to CUSTOM on Reddit.

NexusClient.track(eventName, properties?, context?)

Queues an event for delivery. Returns immediately (fire-and-forget).

  • eventName — required, 1-256 characters (use NexusEvents.* constants or custom strings)
  • properties — optional key-value data (e.g., { value: 4.99, currency: 'USD' })
  • context — optional event context (e.g., { locale: 'en_US' })

The SDK automatically adds:

  • timestamp — current Unix time in seconds
  • context.platform'ios' or 'android' via Platform.OS
  • context.os_version — OS version (e.g., "18.0")
  • context.device_model — device model (requires react-native-device-info on iOS)
  • context.device_manufacturer — device manufacturer (e.g., "Samsung", "Apple")
  • context.locale — device locale (e.g., "en_US")
  • context.screen_width / context.screen_height — screen dimensions in points
  • context.screen_density — pixel ratio (e.g., "3.00")
  • context.app_package — bundle ID / package name (requires react-native-device-info)
  • context.app_version — app version string (requires react-native-device-info)
  • user_id / anonymous_id — from the current identity (persisted across sessions)
  • properties.advertiser_tracking_enabled0 or 1 based on iOS ATT status (always 1 on Android, defaults to 0 if react-native-tracking-transparency is not installed)
  • properties.utm_source / properties.utm_medium / properties.utm_campaign — from Google Play Install Referrer (Android only)
  • properties.gclid — Google Ads click ID from Install Referrer
  • properties.rdt_cid — Reddit click ID from Install Referrer

The server further enriches with event_id, context.ip, and context.user_agent. The worker maps device context fields to Meta's extinfo array format.

Destination-specific properties

To trigger conversions in Meta or Reddit, include these in properties:

| Property | Type | Purpose | |---|---|---| | value | number | Conversion value | | currency | string | ISO currency code (e.g., USD) | | email | string | User email (sent raw; server hashes for Meta/Reddit) | | phone | string | User phone (sent raw; server hashes for Meta/Reddit) | | rdt_cid / click_id | string | Reddit click ID for CAPI attribution | | fbc | string | Meta click ID | | fbp | string | Meta browser ID |

NexusClient.handleDeepLink(url)

Extracts fbclid from a deep link URL and constructs a Meta Click ID (fbc) in the format fb.1.{timestamp}.{fbclid}. The fbc value is persisted to AsyncStorage and automatically attached to all subsequent track() calls. Works on both iOS and Android.

Call this when the app is opened via a deep link (e.g., from a Facebook ad click):

import { Linking } from 'react-native';
import { NexusClient } from 'sng-nexus';

// Handle deep link that launched the app
const url = await Linking.getInitialURL();
if (url) NexusClient.handleDeepLink(url);

// Handle deep links while the app is running
Linking.addEventListener('url', ({ url }) => {
  NexusClient.handleDeepLink(url);
});

If the URL does not contain an fbclid parameter, the call is a no-op.

NexusClient.setInstallReferrer(referrer)

Manually set the Google Play Install Referrer string. Parses and persists utm_source, utm_medium, utm_campaign, gclid, rdt_cid, and fbclid. All subsequent track() calls automatically include these as properties.

Use this if you fetch the referrer from your own native code instead of relying on the SDK's automatic RNInstallReferrer native module detection.

// Example: passing referrer from a native Android module
const referrer = await MyNativeModule.getInstallReferrer();
await NexusClient.setInstallReferrer(referrer);
// referrer format: "utm_source=google&utm_medium=cpc&utm_campaign=spring&gclid=abc123"

NexusClient.isFromAd()

Returns true if the install was attributed to a paid ad. Checks (in order):

  1. fbc present (Meta deep link click)
  2. gclid present (Google Ads click from Install Referrer)
  3. utm_medium is cpc or cpm
  4. utm_source matches a known ad platform (google, facebook, fb, instagram, ig, meta, reddit, tiktok, tiktok_int)
if (NexusClient.isFromAd()) {
  // User arrived via paid campaign — show different onboarding, etc.
}

Google Play Install Referrer

On Android, the SDK automatically captures attribution data from the Google Play Install Referrer API when a native module named RNInstallReferrer is available. This provides:

| Parameter | Description | Example | |---|---|---| | utm_source | Campaign source | google, facebook | | utm_medium | Campaign medium | cpc, cpm, organic | | utm_campaign | Campaign name | spring_sale_2024 | | gclid | Google Ads click ID | EAIaIQ... | | rdt_cid | Reddit click ID | abc123... | | fbclid | Facebook click ID (converted to fbc) | IwAR3... |

These values are fetched once on first launch, persisted to AsyncStorage, and automatically injected into every track() call as event properties. The install event is deferred until the referrer is fetched so it includes full attribution data.

The SDK looks for a native module named RNInstallReferrer with a getReferrer() method that returns { installReferrer: string }. Provide this via an Expo config plugin (see below) or a custom native module.

Alternative (manual): Call NexusClient.setInstallReferrer(referrerString) from your own native code.

Expo Config Plugin for Install Referrer

Create a config plugin that embeds the native module directly in your app — no third-party npm package needed:

// plugins/withInstallReferrer.js
const { withAppBuildGradle, withDangerousMod, withMainApplication } = require('expo/config-plugins');
const fs = require('fs');
const path = require('path');

// Adjust the package name to match your app
const JAVA_PACKAGE = 'com.yourapp.installreferrer';
const JAVA_DIR = `app/src/main/java/${JAVA_PACKAGE.replace(/\./g, '/')}`;

const MODULE_JAVA = `
package ${JAVA_PACKAGE};

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import android.os.RemoteException;
import com.android.installreferrer.api.InstallReferrerClient;
import com.android.installreferrer.api.InstallReferrerStateListener;
import com.android.installreferrer.api.ReferrerDetails;

public class RNInstallReferrerModule extends ReactContextBaseJavaModule {
  private final ReactApplicationContext reactContext;
  public RNInstallReferrerModule(ReactApplicationContext reactContext) {
    super(reactContext);
    this.reactContext = reactContext;
  }
  @Override public String getName() { return "RNInstallReferrer"; }
  @ReactMethod
  public void getReferrer(final Promise promise) {
    InstallReferrerClient client = InstallReferrerClient.newBuilder(reactContext).build();
    client.startConnection(new InstallReferrerStateListener() {
      @Override public void onInstallReferrerSetupFinished(int responseCode) {
        WritableMap result = new WritableNativeMap();
        if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
          try {
            ReferrerDetails d = client.getInstallReferrer();
            result.putString("installReferrer", d.getInstallReferrer());
            result.putString("installTimestamp", String.valueOf(d.getInstallBeginTimestampSeconds()));
            result.putString("clickTimestamp", String.valueOf(d.getReferrerClickTimestampSeconds()));
            client.endConnection();
          } catch (RemoteException e) { result.putString("error", e.getMessage()); }
        } else {
          result.putString("message", "RESPONSE_CODE_" + responseCode);
        }
        promise.resolve(result);
      }
      @Override public void onInstallReferrerServiceDisconnected() {}
    });
  }
}
`.trim();

const PACKAGE_JAVA = `
package ${JAVA_PACKAGE};

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class RNInstallReferrerPackage implements ReactPackage {
  @Override public List<NativeModule> createNativeModules(ReactApplicationContext ctx) {
    List<NativeModule> m = new ArrayList<>();
    m.add(new RNInstallReferrerModule(ctx));
    return m;
  }
  @Override public List<ViewManager> createViewManagers(ReactApplicationContext ctx) {
    return Collections.emptyList();
  }
}
`.trim();

function withInstallReferrer(config) {
  // Write Java source files
  config = withDangerousMod(config, ['android', (config) => {
    const dir = path.join(config.modRequest.platformProjectRoot, JAVA_DIR);
    fs.mkdirSync(dir, { recursive: true });
    fs.writeFileSync(path.join(dir, 'RNInstallReferrerModule.java'), MODULE_JAVA + '\\n');
    fs.writeFileSync(path.join(dir, 'RNInstallReferrerPackage.java'), PACKAGE_JAVA + '\\n');
    return config;
  }]);

  // Add gradle dependency
  config = withAppBuildGradle(config, (config) => {
    let c = config.modResults.contents;
    if (!c.includes('installreferrer')) {
      c = c.replace(/dependencies\\s*\\{/, "dependencies {\\n    implementation 'com.android.installreferrer:installreferrer:2.2'");
    }
    config.modResults.contents = c;
    return config;
  });

  // Register in MainApplication (Kotlin — Expo SDK 55+)
  config = withMainApplication(config, (config) => {
    let c = config.modResults.contents;
    if (!c.includes('RNInstallReferrerPackage')) {
      c = c.replace(/^(package .+)$/m, \`$1\\n\\nimport ${JAVA_PACKAGE}.RNInstallReferrerPackage\`);
      c = c.replace(/(PackageList\\(this\\)\\.packages\\.apply\\s*\\{)/, '$1\\n          add(RNInstallReferrerPackage())');
    }
    config.modResults.contents = c;
    return config;
  });

  // ProGuard keep rule
  config = withDangerousMod(config, ['android', (config) => {
    const p = path.join(config.modRequest.platformProjectRoot, 'app', 'proguard-rules.pro');
    let c = fs.readFileSync(p, 'utf-8');
    if (!c.includes('installreferrer')) {
      c += '\\n# Google Play Install Referrer API\\n-keep public class com.android.installreferrer.** { *; }\\n';
      fs.writeFileSync(p, c);
    }
    return config;
  }]);

  return config;
}

module.exports = withInstallReferrer;

Register in app.json:

{
  "expo": {
    "plugins": ["./plugins/withInstallReferrer"]
  }
}

Bare React Native (no Expo): Copy the Java files into your project manually, add implementation 'com.android.installreferrer:installreferrer:2.2' to app/build.gradle, register RNInstallReferrerPackage in MainApplication, and add the ProGuard keep rule.

refreshATTStatus()

Re-reads the iOS ATT tracking status. Call this after presenting the ATT prompt so subsequent track() calls reflect the user's choice. On Android this is a no-op.

import { requestTrackingPermission } from 'react-native-tracking-transparency';
import { refreshATTStatus } from 'sng-nexus';

const status = await requestTrackingPermission();
await refreshATTStatus();

NexusClient.flush()

Forces an immediate flush of all queued events. Returns a promise that resolves when the flush cycle completes. Skips silently if offline or already flushing.

NexusClient.resetIdentity()

Clears the current user identity. Call on logout.

NexusClient.shutdown()

Stops the flush timer and network listener, persists the queue, and marks the client as uninitialized. Call when the app is terminating.

Offline Queue & Retry Behavior

Events are persisted to AsyncStorage under the key @nexus:event_queue. This means events survive app kills, crashes, and reboots. The install event flag is stored under @nexus:install_sent to ensure it fires only once per device.

Flush triggers

A flush is attempted when:

  1. The flush timer fires (every flushInterval seconds)
  2. The device transitions from offline to online
  3. flush() is called manually
  4. The queue reaches 10 events (batch threshold)

Retry strategy

| Scenario | Behavior | |---|---| | Network offline | Events stay in queue, flushed on reconnect | | Server 5xx | Retry with exponential backoff (1s, 2s, 4s, 8s, 16s... capped at 30s) + jitter | | Server 4xx (validation/auth error) | Event dropped immediately, no retry | | App killed | Events persist in AsyncStorage, flushed on next init() | | Max retries exceeded | Event dropped from queue | | Queue full | Oldest events dropped to make room for new ones |

Backoff formula

delay = min(1000ms * 2^attempt, 30000ms) + random jitter (0-25%)

Architecture

src/
├── index.ts      — Public exports
├── client.ts     — NexusClient singleton (init, track, identify, flush, shutdown, isFromAd)
├── types.ts      — TypeScript interfaces (NexusConfig, TrackPayload, etc.)
├── queue.ts      — AsyncStorage-backed persistent event queue
├── network.ts    — NetInfo connectivity monitor with reconnect callback
├── retry.ts      — Exponential backoff delay calculator
├── att.ts        — iOS ATT status reader (optional react-native-tracking-transparency)
├── device.ts     — Device info collector for Meta CAPI extinfo (optional react-native-device-info)
└── referrer.ts   — Google Play Install Referrer parser + UTM attribution (via RNInstallReferrer native module)

Data flow

track() → EventQueue (in-memory + AsyncStorage)
                ↓ flush trigger
         fetch POST /api/track
                ↓ success
         remove from queue
                ↓ 5xx failure
         increment retry, keep in queue
                ↓ 4xx failure
         drop from queue (bad request)

Integration Example

App.tsx (Expo / bare RN)

import { useEffect } from 'react';
import { AppState, Linking } from 'react-native';
import { NexusClient } from 'sng-nexus';

export default function App() {
  useEffect(() => {
    async function bootstrap() {
      await NexusClient.init({
        endpoint: 'https://nexus.yourserver.com',
        apiKey: 'myapp:your-secret',
        debug: __DEV__,
      });

      // Capture fbc from the deep link that launched the app
      const initialUrl = await Linking.getInitialURL();
      if (initialUrl) NexusClient.handleDeepLink(initialUrl);
    }
    bootstrap();

    // Capture fbc from deep links while the app is running
    const linkSub = Linking.addEventListener('url', ({ url }) => {
      NexusClient.handleDeepLink(url);
    });

    const appStateSub = AppState.addEventListener('change', (state) => {
      if (state === 'background') {
        NexusClient.flush();
      }
    });

    return () => {
      linkSub.remove();
      appStateSub.remove();
      NexusClient.shutdown();
    };
  }, []);

  return <MainNavigator />;
}

After login

NexusClient.identify({ userId: user.id });

Tracking events

import { NexusClient, NexusEvents } from 'sng-nexus';

// Standard event with Meta/Reddit conversion data
NexusClient.track(NexusEvents.PURCHASE, {
  value: 4.99,
  currency: 'USD',
  email: user.email,
});

// Custom event (passes as-is to Meta, CUSTOM on Reddit)
NexusClient.track('level_complete', { level: 5, score: 100 });

On logout

NexusClient.resetIdentity();

Development

# From the monorepo root
pnpm --filter sng-nexus build    # Compile TypeScript
pnpm --filter sng-nexus clean    # Remove dist/

The package uses the monorepo's shared tsconfig.base.json and builds with tsc to dist/.