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

velora-mobile-sdk

v0.1.5

Published

Analytics SDK for React Native and Expo

Readme

Velora Mobile SDK

Production analytics SDK for React Native and Expo apps with offline queueing, batching, retry/backoff, and lifecycle-aware delivery.

Why teams ship this in production

  • Reliable delivery with persisted AsyncStorage queue
  • Automatic flushing on interval, app background, and reconnect
  • Retry with exponential backoff (failed events stay queued)
  • Identity and session lifecycle built in
  • Optional auto-tracking for app lifecycle and route changes
  • Safe property sanitization (drops unsupported values, trims long strings)

Installation

npm install velora-mobile-sdk

Required peer dependencies:

npm install react react-native @react-native-async-storage/async-storage

Optional peers (recommended):

npm install @react-native-community/netinfo expo-application expo-device expo-linking expo-localization

Quick Start (production baseline)

Initialize once at app bootstrap (for example in your root component setup):

import { initAnalytics } from "velora-mobile-sdk";

await initAnalytics({
  apiKey: process.env.EXPO_PUBLIC_VELORA_API_KEY ?? "",
  baseUrl: "https://api.example.com", // optional, defaults to https://api.veloraapp.io
  debug: __DEV__,
  autoTrackLifecycle: true,
  autoTrackScreens: true,
  normalizeScreenNames: true,
  autoTrackDeepLinks: true,
});

Track events and screens:

import { analytics } from "velora-mobile-sdk";

await analytics.track("Offer Viewed", {
  location: "OfferScreen",
  properties: { offerId: "offer_123", placement: "hero" },
});

await analytics.screen("OfferScreen", { offerId: "offer_123" });

Identify after auth, and reset on logout/account switch:

await analytics.identify({ userId: "user_123" });
await analytics.reset();

Configuration

type AnalyticsConfig = {
  apiKey: string;
  baseUrl?: string; // default: "https://api.veloraapp.io"
  batchSize?: number; // default: 20
  flushIntervalMs?: number; // default: 10000
  sessionTimeoutMs?: number; // default: 30 minutes
  debug?: boolean; // default: false
  autoTrackLifecycle?: boolean; // default: true
  autoTrackScreens?: boolean; // default: true (effective behavior)
  normalizeScreenNames?: boolean; // default: true (effective behavior)
  autoTrackDeepLinks?: boolean; // default: true
};

Public API

import {
  analytics,
  initAnalytics,
  normalizeLocation,
  useExpoRouterTracking,
  TrackedPressable,
  TrackedTouchableOpacity,
  createScrollTracker,
} from "velora-mobile-sdk";

Core methods:

  • analytics.track(name, { properties?, location? })
  • analytics.screen(name, properties?)
  • analytics.identify({ userId })
  • analytics.flush()
  • analytics.reset()
  • analytics.updateLocation(location)
  • analytics.getLocation()
  • analytics.shutdown()

Navigation Tracking

Expo Router (RootLayout pattern)

Use this when your app is powered by Expo Router and routing state lives in the root layout:

import { Slot, usePathname } from "expo-router";
import React, { useEffect } from "react";
import { initAnalytics, useExpoRouterTracking } from "velora-mobile-sdk";

export default function RootLayout() {
  const route = usePathname();

  useEffect(() => {
    void initAnalytics({
      apiKey: process.env.EXPO_PUBLIC_VELORA_API_KEY ?? "",
      autoTrackLifecycle: true,
      autoTrackScreens: true,
      autoTrackDeepLinks: true,
    });
  }, []);

  // Tracks route changes from Expo Router
  useExpoRouterTracking(route);
  
}

Notes:

  • Initialize analytics once at app bootstrap.
  • Pass the current pathname from usePathname() into useExpoRouterTracking(pathname).
  • If you use useExpoRouterTracking, do not also call analytics.updateLocation(...) for the same route changes.

React Navigation bridge

import { NavigationContainer, useNavigationContainerRef } from "@react-navigation/native";
import { analytics } from "velora-mobile-sdk";

export default function AppNavigation() {
  const navigationRef = useNavigationContainerRef();

  return (
    <NavigationContainer
      ref={navigationRef}
      onStateChange={() => {
        const route = navigationRef.getCurrentRoute();
        void analytics.updateLocation(route?.name ?? null);
      }}
    >
      {/* screens */}
    </NavigationContainer>
  );
}

updateLocation can emit:

  • Page Viewed (auto-tracking enabled)
  • Page Exited with durationMs when changing screens
  • Page Exited with reason: "app_background" when app goes to background

Deep Link Attribution

Deep-link tracking is enabled by default (autoTrackDeepLinks: true) and uses expo-linking when available.

Supported query params:

  • Velora params: vl_source, vl_medium, vl_campaign, vl_content, vl_term, vl_link_id
  • UTM fallbacks: utm_source, utm_medium, utm_campaign, utm_content, utm_term
  • Alias fallback for link id: vlinkId

Behavior:

  • Parsed attribution is stored as pending (velora_pending_attr)
  • On the next new session, pending attribution is promoted to current (velora_current_attr)
  • Session Started includes attribution fields in properties
  • Every event carries trackingLinkId and context.attribution for the active session

UI Tracking Helpers

<TrackedPressable
  eventName="CTA Clicked"
  eventProperties={{ placement: "hero" }}
  onPress={handlePress}
/>
<TrackedTouchableOpacity
  eventName="Buy Clicked"
  eventProperties={{ sku: "premium_monthly" }}
  onPress={handleBuy}
/>

Scroll Depth Tracking

import { createScrollTracker, analytics } from "velora-mobile-sdk";

const trackScrollDepth = createScrollTracker(analytics, {
  screen: "ArticleScreen",
  contentHeight: 2400,
});

// call from onScroll handler
trackScrollDepth(scrollY, viewportHeight);

Emits Scroll Depth Reached at 25/50/75/100% milestones.

Delivery and retry behavior

  • Queue is persisted in AsyncStorage and survives restarts
  • Flush triggers: interval, reconnect, app background, and queue-size threshold
  • Flush is skipped when offline or when another flush is already in progress
  • Batch size defaults to 20
  • On failure, events are re-queued with incremented retry count
  • Retries use exponential backoff (1s, 2s, 4s, ...)
  • Events are retried up to 5 times, then dropped

Backend contract

flush() sends POST {baseUrl}/v1/events/batch with headers:

  • Content-Type: application/json
  • X-API-Key: <apiKey>
  • X-Request-Id: <generated>
  • X-Idempotency-Key: <generated>

Payload shape:

{
  "sentAt": "2026-04-12T00:00:00.000Z",
  "sdk": { "name": "velora-mobile-sdk", "version": "0.1.x" },
  "events": [
    {
      "clientEventId": "evt_...",
      "name": "Offer Viewed",
      "occurredAt": "2026-04-12T00:00:00.000Z",
      "userId": "user_123",
      "anonymousId": null,
      "sessionId": "sess_...",
      "trackingLinkId": "vlink_...",
      "properties": { "offerId": "offer_123" },
      "context": {
        "platform": "ios",
        "appVersion": "1.0.0",
        "os": "iOS 18.0",
        "device": "iPhone 15",
        "locale": "en-US",
        "timezone": "America/New_York",
        "location": "OfferScreen",
        "sdk": { "name": "velora-mobile-sdk", "version": "0.1.x" },
        "attribution": {
          "entryType": "deep_link",
          "source": "instagram",
          "campaign": "spring_launch",
          "vlinkId": "vlink_123"
        }
      }
    }
  ]
}

Automatic events

When session starts (at init or after timeout):

  • Session Started (auto-tracked)

When autoTrackLifecycle: true (default):

  • Application Opened
  • Application Backgrounded
  • Application Foregrounded

When location tracking is used (updateLocation / useExpoRouterTracking):

  • Page Viewed (unless autoTrackScreens: false)
  • Page Exited (only when screen duration is at least 300ms)

Data handling

Stored in AsyncStorage:

  • Anonymous ID (velora_sdk_anonymous_id)
  • User ID (velora_sdk_user_id)
  • Session ID + last activity (velora_sdk_session_id, velora_sdk_last_activity_at)
  • Event queue (velora_sdk_event_queue)
  • Attribution (velora_pending_attr, velora_current_attr)
  • Lifecycle metadata (velora:first_open_at, velora:last_open_at)

Property sanitization:

  • Drops undefined and function values
  • Keeps JSON-serializable objects
  • Trims string values to 1000 chars

Production checklist

  • Initialize exactly once at startup
  • Use a real production API key and endpoint
  • Keep debug: false in production builds
  • Wire route changes to updateLocation (or useExpoRouterTracking)
  • Call identify only after auth is confirmed
  • Call reset on logout/account switch
  • Ensure backend handles idempotency headers
  • Validate optional peers are installed for richer device/network context
  • Call analytics.shutdown() during controlled app teardown/testing flows

License

ISC