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

@brightmotion/agenthog-react-native

v0.8.0

Published

AgentHog analytics for React Native / Expo — sessions, screen views, tap autocapture, scroll depth, identity. Speaks CONTRACTS.md directly.

Readme

@brightmotion/agenthog-react-native

AgentHog analytics for React Native / Expo. Speaks CONTRACTS.md (POST /ingest) directly — sessions, screen views (pageview: <path>), tap autocapture (click: <label> via fiber walk), per-screen time (leave with duration_s/max_scroll), scroll depth, custom events, identity. Spec: docs/RN_SDK_SPEC.md. Pure JS — no native code, no runtime deps.

import { AgentHogProvider, useAgentHog, useScreenTracking, useScrollDepth } from '@brightmotion/agenthog-react-native'
import { asyncStorage } from '@brightmotion/agenthog-react-native/async-storage'
import { expoInstallReferrer } from '@brightmotion/agenthog-react-native/install-referrer'

// root layout — wrap the app (inside your gesture-handler root)
<AgentHogProvider config={{
  host: 'https://hog.brightmotion.io',
  projectKey: process.env.EXPO_PUBLIC_AGENTHOG_KEY ?? '',
  enabled: !!process.env.EXPO_PUBLIC_AGENTHOG_KEY,
  appName: 'myapp', appVersion: '1.0.0',
  storage: asyncStorage,                // omit → in-memory, ids reset every launch
  installReferrer: expoInstallReferrer, // Expo: automatic install attribution (see below)
}}>
  {children}
</AgentHogProvider>

// anywhere inside — screens + identity
useScreenTracking(usePathname())        // expo-router; feed any router's pathname
const ah = useAgentHog()
ah.capture('photo_sent', { recipients: 3 })
ah.identify(email, { clerk_id })        // email = cross-device stitch key
// Manual campaign attribution, as if the session arrived on a `?utm_...` web
// URL. With `installReferrer` configured this happens automatically on first
// launch; keep this for other sources (web handoffs, invite codes). Call as
// early as possible; params set after the session's first flush don't take effect.
ah.setLandingParams({ utm_source: 'web', utm_campaign: 'invite' })
// MMP attach (e.g. Singular's attribution callback) → the session's utm_* columns.
// Callable any time; delivered once per distinct payload; deep-link params still win.
ah.setAttribution({ provider: 'singular', utm_source: network, utm_campaign, params: { campaign_id } })
ah.reset()                              // sign-out

// a scrollable screen's primary scroller (optional, for scroll-depth events)
<FlatList {...useScrollDepth()} ... />

Install attribution (0.4.0)

Pass an installReferrer provider and the SDK does install attribution by itself, the way MMP SDKs do: once per install, on first launch, gated ahead of the session's first flush (≤1.5 s, local IPC), it reads the Play Install Referrer, feeds its plaintext utm_* into the landing-params pipe (session utm columns → Top sources / campaigns), and ships the raw referrer string as context.install — the server classifies it and decrypts Meta's encrypted campaign payload with the key from your AgentHog project settings (Settings → Install attribution). No decryption key ever ships in the app.

// Expo (expo-application is already in the Expo SDK):
import { expoInstallReferrer } from '@brightmotion/agenthog-react-native/install-referrer'
installReferrer: expoInstallReferrer

// Bare RN — bring your own reader (e.g. the play-install-referrer package):
installReferrer: async () => (await PlayInstallReferrer.getInstallReferrerInfo()).installReferrer

iOS resolves null (the App Store has no referrer — that's Apple's design, not a gap); sideloads and Play-less devices read as organic/none. Needs storage configured — the once-per-install flag lives there.

The server answers the install batch with its classification, which the SDK caches for the app's lifetime and exposes (0.5.0) — use it to put campaign props on events or person traits:

ah.onAttribution(({ source, utm, meta }) => {   // fires once; replayed from cache on later launches
  ah.register({ attribution_source: source, ...utm })
  if (meta) ah.identify(undefined, { meta_campaign: meta.campaign_group_name })
})
const cached = await ah.getAttribution()        // null until the install batch round-trips

Storage

Persistence is opt-in: pass storage, or anon ids and the queued event buffer live in memory only and every app restart looks like a brand-new visitor. Two options:

import { asyncStorage } from '@brightmotion/agenthog-react-native/async-storage'  // needs the peer
storage: asyncStorage
storage: myAdapter   // any { getItem, setItem, removeItem } returning promises (MMKV, SQLite, …)

Install the peer for the first form: npx expo install @react-native-async-storage/async-storage.

The SDK can't find AsyncStorage for you, and the separate entry point is why. Metro collects dependencies statically from literal import/require forms, so a lazy lookup never enters the bundle graph — and when it fails, Metro's guardedLoadModule routes the error to ErrorUtils.reportFatalError (a dev redbox that no surrounding try/catch can suppress) before returning undefined. Versions ≤0.2.0 tried exactly that and got both halves wrong: a crash and a silent fall back to memory. A literal top-level import in the main entry would resolve correctly but make the optional peer mandatory for everyone, breaking the bundle for consumers who bring their own adapter. Importing /async-storage is what puts the peer in the graph, and only for the apps that actually want it.

Dev: bun test packages/react-native. Ship to a consumer repo: npm pack → commit the tarball to <app>/vendor/npm i file:vendor/agenthog-react-native-<v>.tgz.