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

@inoxth/react-native-edot-sdk

v0.2.0

Published

EDOT React Native SDK — OpenTelemetry-compliant observability wrapping native EDOT SDKs

Readme

@inoxth/react-native-edot-sdk

OpenTelemetry-compliant observability SDK for React Native. Wraps the native EDOT iOS and EDOT Android agents to provide automatic and manual instrumentation with zero-config setup. Supports both Old Architecture (Bridge) and New Architecture (TurboModules/Fabric) from a single codebase.

What you get

  • React init hookuseEdot(config) for declarative initialization with reactive { ready, error } state; the imperative EdotReactNative.initialize(config) is also available for non-React contexts
  • Network instrumentation — automatic span creation for fetch and XMLHttpRequest (including Axios) with W3C trace context propagation
  • Error tracking — captures uncaught JS exceptions, unhandled Promise rejections, and React render errors via EdotErrorBoundary
  • Startup tracing — cold start performance with AppStartup: js_bundle_load and AppStartup: first_render child spans under an AppStartup: cold parent
  • App-state tracking — foreground/background screen-lifetime spans with active-screen replay on resume
  • Lifecycle events — emitted natively by the EDOT iOS/Android agents per the Elastic mobile agents spec
  • App + system metricsapplication.launch.time histogram plus system.cpu.usage and system.memory.usage observable gauges on both platforms (iOS via MetricKit + Mach task APIs; Android via Choreographer + Process.getElapsedCpuTime + Debug.MemoryInfo)
  • User interactionswithEdotTracking HOC and useEdotAction hook for tap/action tracking
  • Manual instrumentation — see @inoxth/react-native-edot-tracer-provider for custom spans and metrics
  • Navigation tracking — see @inoxth/react-native-edot-navigation for screen spans
  • Ignore filtersignoreSpanNames, ignoreLogPatterns for noise control on both platforms

Install

yarn add @inoxth/react-native-edot-sdk

iOS

cd ios && pod install

That's it. The SDK podspec declares the EDOT iOS agent (apm-agent-ios) as a Swift Package dependency via React Native's spm_dependency helper, so pod install resolves the package and links the ElasticApm product onto the SDK's pod target automatically. No manual Xcode SPM configuration is required.

On Expo, run npx expo prebuild (or npx expo run:ios) instead — it generates the ios/ project and runs pod install for you.

Requires React Native 0.75+ and CocoaPods 1.13+. The SDK's native files are conditionally compiled against ELASTIC_APM_AVAILABLE, which the podspec enables on its own pod target whenever spm_dependency is in scope.

Android

Apply the EDOT Android Gradle plugin — it provides the co.elastic.otel.android runtime the SDK links against.

android/build.gradle (project-level):

buildscript {
  dependencies {
    classpath("co.elastic.otel.android.agent:co.elastic.otel.android.agent.gradle.plugin:1.1.0")
  }
  repositories {
    google()
    mavenCentral()
    gradlePluginPortal()
  }
}

android/app/build.gradle:

apply plugin: "co.elastic.otel.android.agent"

Requires Gradle 8.7+, AGP 8.9.1+, compileSdk 36, minSdk 24. See example/react-navigation/android/ for a reference setup.

No extra command needed — RN Gradle autolinking wires the SDK on the next yarn android / ./gradlew build.

⚠️ Do not apply co.elastic.otel.android.instrumentation.okhttp. RN's fetch / XHR are already instrumented at the JS layer — adding the OkHttp Gradle plugin would emit a second span per HTTP call, doubling APM ingest cost. If you need OkHttp instrumentation for non-RN code paths in the same app, install an interceptor that skips requests carrying the X-Edot-RN-Traced header (set by the JS-layer instrumentation).

ℹ️ Optional — opt out of READ_PHONE_STATE. The EDOT Android agent (agent-sdk 1.1.0) declares the READ_PHONE_STATE "dangerous" permission, used only for an optional cellular network-subtype attribute. It is runtime-guarded (crash-safe when ungranted) and was removed upstream in agent-sdk 1.3.1. To strip it from your merged manifest, add this to android/app/src/main/AndroidManifest.xml (ensure the root <manifest> tag has xmlns:tools="http://schemas.android.com/tools"):

<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />

Initialize

import { EdotReactNative } from '@inoxth/react-native-edot-sdk';

await EdotReactNative.initialize({
  serverUrl: 'https://your-apm-server:8200',
  serviceName: 'my-app',
  serviceVersion: '1.0.0',
  deploymentEnvironment: 'production',
  secretToken: process.env.EDOT_SECRET_TOKEN,
});

Auto-instrumentation for network, errors, and startup is enabled by default. Lifecycle events (event.name="lifecycle", event.domain="device") are emitted natively by the EDOT iOS / Android agents per the Elastic mobile agents spec.

To report iOS and Android as distinct services in the Elastic APM service map, supply serviceName per platform — top-level serviceName then becomes optional:

await EdotReactNative.initialize({
  serverUrl: 'https://your-apm-server:8200',
  serviceVersion: '1.0.0',
  deploymentEnvironment: 'production',
  ios: { serviceName: 'myapp-ios' },
  android: { serviceName: 'myapp-android' },
});

The platform-specific serviceName overrides the top-level value when both are present. At least one must resolve for the active platform.

React init hook

For React apps, useEdot(config) is the idiomatic entry point — it calls initialize once on mount and exposes reactive { ready, error } state:

import { useEdot, EdotErrorBoundary } from '@inoxth/react-native-edot-sdk';
import { ActivityIndicator, Text } from 'react-native';

export function App() {
  const { ready, error } = useEdot({
    serverUrl: 'https://your-apm-server:8200',
    serviceName: 'my-app',
    serviceVersion: '1.0.0',
    deploymentEnvironment: 'production',
    secretToken: process.env.EDOT_SECRET_TOKEN,
  });

  if (error) {
    return <Text>Telemetry unavailable: {error.message}</Text>;
  }
  if (!ready) {
    return <ActivityIndicator />;
  }

  return (
    <EdotErrorBoundary fallback={<Text>Something went wrong</Text>}>
      <RootNavigator />
    </EdotErrorBoundary>
  );
}

Behavior:

  • First-wins. Config is captured on the first render and reused on every subsequent render — re-renders never re-initialize. In __DEV__, a console.warn fires when a native-relevant primitive key (serverUrl, serviceName, secretToken, etc.) changes after first render.
  • Passive errors. Init failures are stored in error and warned once to the console — never thrown. Observability degrades silently rather than crashing the app.
  • StrictMode-safe. The underlying singleton guard in EdotReactNative.initialize short-circuits duplicate calls, so React 18+ double-mount in dev costs nothing.

For navigation-aware apps, mount the navigation root only once ready flips to true so the initial screen span is captured by the active tracer provider — see @inoxth/react-native-edot-navigation.

Host-app pre-initialization (advanced)

If you need cold-start spans from native code that runs before the JS bundle loads, start the agent from the host app's entry point:

iOS — call from application(_:didFinishLaunchingWithOptions:):

import EdotReactNative

EdotReactNativeAgent.preInitialize(
  serverUrl: "https://your-apm-server:8200",
  serviceName: "myapp-ios",
  serviceVersion: "1.0.0",
  deploymentEnvironment: "production",
  secretToken: "..."
)

Android — call from MainApplication.onCreate():

import com.edot.reactnative.EdotReactNativeAgent

EdotReactNativeAgent.preInitialize(
  application = this,
  serverUrl = "https://your-apm-server:8200",
  serviceName = "myapp-android",
  serviceVersion = "1.0.0",
  deploymentEnvironment = "production",
  secretToken = "...",
)

Both signatures accept the same optional auth + transport surface as JS init: secretToken, apiKey, sessionSamplingRate, exportProtocol, plus diskBufferingEnabled (Android). When pre-initialized, the JS initialize() call skips agent start and (under debug) logs any reserved fields it receives that pre-init should have owned, since the agent is already running.

Configuration

EdotReactNative.initialize(config) accepts the following options. The full type is exported as EdotConfig.

Required

| Option | Type | Description | | ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | serverUrl | string | EDOT / APM server URL | | serviceName | string | Service name (no , or =). Optional if ios.serviceName / android.serviceName is set for the active platform; the per-platform value wins when both are present. | | serviceVersion | string | Service version (no , or =) | | deploymentEnvironment | string | production, staging, etc. |

Authentication (pick one)

| Option | Type | Description | | ------------- | -------- | ----------------------------------------------- | | secretToken | string | Secret token. Mutually exclusive with apiKey. | | apiKey | string | API key. Mutually exclusive with secretToken. |

Both are wrapped in a redacted-string container immediately on receipt — JSON.stringify(config) will not leak them.

Auto-instrumentation toggles

| Option | Type | Default | Description | | --------------------------- | --------- | ------- | ------------------------------------------- | | instrumentNetworkRequests | boolean | true | fetch + XHR spans | | instrumentJsErrors | boolean | true | Uncaught exceptions + unhandled rejections. Non-fatal errors are recorded as OTel exception events on the active view span (when one exists) or as event.name=exception log records. Fatal errors are emitted as event.name=crash, event.domain=device log records per the Elastic mobile crash event spec, so JS crashes surface alongside native crashes in Kibana. | | instrumentAppStartup | boolean | true | Cold/warm start spans | | appStateTracking | boolean | true | Foreground/background screen-lifetime spans |

Network filtering

| Option | Type | Description | | ------------------------- | ------------------------- | -------------------------------------------------------------------- | | tracePropagationTargets | (string \| RegExp)[] | Allowlist of URLs to inject traceparent into. Default (omitted): inject on all outbound requests (matching the iOS native agent), except serverUrl / ignoreUrls. Pass [] to opt out entirely. | | ignoreUrls | (string \| RegExp)[] | URLs to skip span creation for. | | graphqlUrls | (string \| RegExp)[] | URLs treated as GraphQL endpoints. Operation type + name are parsed from the body; spans are named <type> <name> (e.g. query GetUser) per OTel GraphQL semconv, and graphql.operation.type / graphql.operation.name attributes are set. | | urlSanitizer | (url: string) => string | Strip secrets/PII from http.url before export. |

Sampling & consent

| Option | Type | Description | | --------------------- | ----------------------------------------- | ------------------------------------------------ | | sessionSamplingRate | number | 0.01.0. Defaults to native agent's default. | | trackingConsent | 'granted' \| 'pending' \| 'not_granted' | JS-side emission gate. |

Transport

| Option | Type | Description | | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | exportProtocol | 'http' \| 'grpc' | Defaults to 'http' on both platforms. The SDK overrides upstream defaults (apm-agent-ios → gRPC, apm-agent-android → HTTP) so the same omitted-config produces the same transport everywhere. Set explicitly for gRPC. |

Native metrics (cross-platform)

| Option | Type | Default | Description | | -------------------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | enableAppMetricInstrumentation | boolean | true | Emit application.launch.time histogram (iOS via MetricKit, Android via Choreographer). Set false to skip the install. | | enableSystemMetrics | boolean | true | Emit system.cpu.usage and system.memory.usage observable gauges (iOS via Mach task APIs, Android via Process.getElapsedCpuTime + Debug.MemoryInfo). Set false to skip the install. |

Ignore filters

| Option | Type | Description | | --------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | ignoreSpanNames | (string \| { source: string; flags?: string })[] | Drop spans whose name matches any rule. Exact-string or regex-source matches. | | ignoreLogPatterns | Array<{ name?: string \| RegexSource; minSeverity?: 'trace' \| 'debug' \| 'info' \| 'warn' \| 'error' \| 'fatal' }> | Drop log records matching any rule (name match OR severity below minSeverity). |

Real RegExp objects don't survive the React Native bridge, so regex rules use the { source, flags? } shape instead.

Lifecycle / opt-out

| Option | Type | Description | | -------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | disableAgent | boolean | Fully suppresses native agent startup. Distinct from trackingConsent: 'not_granted' (which gates JS-side emission only). Use for test environments / hard opt-out flows. |

iOS-only

Pass these under ios: { … } in the config:

| Option | Type | Description | | ----------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | ios.serviceName | string | Override serviceName on iOS. Falls back to top-level serviceName when omitted. | | ios.enableCrashReporting | boolean | Enable native crash reporting. | | ios.enableURLSessionInstrumentation | boolean | Enable native URLSession HTTP spans. Off by default — JS-side fetch/XHR instrumentation is the canonical path. | | ios.enableViewControllerInstrumentation | boolean | Enable UIViewController lifecycle spans. Off by default — JS navigation plugin is the canonical path. | | ios.enableLifecycleEvents | boolean | Enable foreground/background/inactive/terminate lifecycle events. |

Android-only

Pass these under android: { … } in the config:

| Option | Type | Description | | ------------------------------ | --------- | -------------------------------------------------------------------------------------- | | android.serviceName | string | Override serviceName on Android. Falls back to top-level serviceName when omitted. | | android.diskBufferingEnabled | boolean | Persist signals across process restarts. (Android only.) |

Debug

| Option | Type | Description | | ------- | --------- | ----------------------------------------------- | | debug | boolean | Enables [EDOT] console logs from the JS side. |

Error boundary

Wrap your app to capture render errors as spans:

import { EdotErrorBoundary } from '@inoxth/react-native-edot-sdk';
import { Text } from 'react-native';

export function App() {
  return (
    <EdotErrorBoundary fallback={<Text>Something went wrong</Text>}>
      <RootNavigator />
    </EdotErrorBoundary>
  );
}

Render errors are routed through the same path as uncaught JS exceptions: when an active view exists, they're recorded as an OTel exception event on the view span; otherwise they emit a stand-alone log record with event.name=exception and exception.type / exception.message / exception.stacktrace attributes.

User interactions

withEdotTracking HOC

import { withEdotTracking } from '@inoxth/react-native-edot-sdk';
import { TouchableOpacity } from 'react-native';

const TrackedButton = withEdotTracking(TouchableOpacity, 'CheckoutButton');

// Renders normally; emits a tap action when pressed.
<TrackedButton onPress={handleCheckout}>...</TrackedButton>;

useEdotAction hook

import { useEdotAction } from '@inoxth/react-native-edot-sdk';

function CheckoutScreen() {
  const { trackAction } = useEdotAction();

  function handlePurchase() {
    trackAction('tap', 'Purchase', { 'cart.items': 3 });
  }
}

Tracking consent

import { EdotReactNative } from '@inoxth/react-native-edot-sdk';

EdotReactNative.setTrackingConsent('granted');

setTrackingConsent gates JS-side emission at runtime — 'granted' | 'not_granted' | 'pending'. To suppress the native agent entirely, set disableAgent: true in the init config instead.

Structured logs

EdotReactNative.log('info', 'Payment completed', { orderId: 'ord-456' });
EdotReactNative.log('error', 'Checkout failed', { reason: 'card_declined' });

Severity is one of 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'.

Working example

example/basic/ — SDK init, manual tracing, metrics, logs, network, errors, interactions. No navigation.

Requirements

  • React Native >= 0.75 (required for the spm_dependency Cocoapods helper)
  • iOS >= 15.6
  • Android minSdk 24, compileSdk 36
  • Node.js >= 18

License

MIT — see LICENSE.