
Security News
PolinRider: North Korea-Linked Supply Chain Campaign Expands Across Open Source Ecosystems
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.
@horizoneng/pulse-react-native
Advanced tools
Production-grade observability SDK for React Native applications with OpenTelemetry support. Real-time monitoring, error tracking, performance insights, and distributed tracing.
Production-grade observability for React Native applications
Real-time monitoring, error tracking, and performance insights powered by OpenTelemetry
@horizoneng/pulse-react-native
🚨 Error Monitoring: Capture JavaScript crashes and exceptions with full stack traces.
⚡ Performance Monitoring: Distributed tracing spans for synchronous and asynchronous operations with automatic or manual instrumentation.
🌐 Network Monitoring: Auto-instrument HTTP requests (fetch and XMLHttpRequest) with zero code changes.
🧭 Navigation Tracking: Automatic screen transition monitoring with React Navigation integration.
📊 Event Tracking: Log custom business events and user actions with structured metadata.
🔌 OpenTelemetry Native: Built on OpenTelemetry Android SDK. Automatically captures ANR, frozen frames, activity/fragment lifecycle, network changes, view interactions, and more. See the Android SDK documentation for all native features.
🏗️ Architecture Support: Supports both React Native Old Architecture and New Architecture out of the box.
Note: Currently supports Android only. iOS support is coming soon.
npm install @horizoneng/pulse-react-native
# or
yarn add @horizoneng/pulse-react-native
Initialize the Pulse Android SDK in your MainApplication.kt:
import android.app.Application
import com.pulse.android.sdk.PulseSDK
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize Pulse Android SDK
PulseSDK.INSTANCE.initialize(
application = this,
endpointBaseUrl = <server-url>
)
}
}
Important: This step is mandatory. Without native SDK initialization, no telemetry will be sent.
Advanced Configuration:
For custom endpoints, headers, disk buffering, session settings, ANR thresholds, and other native features, see the Android SDK Initialization Guide.
Note: iOS support is coming soon.
Enable automatic instrumentation in your app entry point (e.g., App.tsx):
import { Pulse } from '@horizoneng/pulse-react-native';
// Enable auto-instrumentation features
Pulse.start();
function App() {
// Your app code
}
What gets automatically tracked:
Note: All
autoInstrument*options are enabled by default. You can disable specific features by setting them tofalse.
Pulse React Native automatically captures critical application events without requiring manual instrumentation. Simply call Pulse.start() to enable automatic monitoring:
Pulse.start(); // All auto-instrumentation enabled by default
1. Unhandled JavaScript Errors
All unhandled JavaScript exceptions and promise rejections are automatically captured with full stack traces. This includes:
The error handler integrates seamlessly with React Native's ErrorUtils, preserving the original error handling chain while capturing telemetry.
2. Network Requests
HTTP requests are automatically instrumented when using standard APIs:
fetch() API callsXMLHttpRequest (XHR) operationsEach network request captures:
3. React Navigation
Screen navigation events are tracked when using React Navigation (requires setup, see React Navigation Integration):
You can selectively disable specific auto-instrumentation features:
Pulse.start({
autoDetectExceptions: false, // Disable crash tracking
autoDetectNetwork: false, // Disable network monitoring
autoDetectNavigation: false // Disable navigation tracking
});
Note: Disabling auto-instrumentation means you'll need to manually track these events if needed.
Pulse automatically tracks screen navigation and route changes when using React Navigation.
import { NavigationContainer } from '@react-navigation/native';
import { Pulse } from '@horizoneng/pulse-react-native';
function App() {
const navigationIntegration = Pulse.createNavigationIntegration();
return (
<NavigationContainer
onReady={(ref) => navigationIntegration.registerNavigationContainer(ref)}
>
{/* Your screens */}
</NavigationContainer>
);
}
Each navigation event includes:
{
'screen.name': 'ProfileScreen', // Current screen
'last.screen.name': 'HomeScreen', // Previous screen
'routeHasBeenSeen': false, // First visit or returning
'routeKey': 'ProfileScreen-abc123', // Unique route identifier
'pulse.type': 'screen_load', // Event type
'phase': 'start' // Navigation phase
}
Requirements:
@react-navigation/native v5.x or higherautoDetectNavigation: true in Pulse.start() (enabled by default)Unhandled JavaScript errors and promise rejections are automatically captured when autoDetectExceptions is enabled (default).
All uncaught errors are reported with:
You can manually report caught exceptions:
try {
await riskyOperation();
} catch (error) {
Pulse.reportException(error);
}
Report as fatal:
Pulse.reportException(error, true); // Second parameter: isFatal
With custom attributes:
Pulse.reportException(error, false, {
userId: '123',
operation: 'checkout',
attempt: 3,
environment: 'production'
});
Supported attribute types: string, number, boolean, and arrays of these types.
Pulse provides a built-in ErrorBoundary component that uses React's Error Boundary API to automatically catch and report errors from inside a React component tree.
import { Pulse } from '@horizoneng/pulse-react-native';
import { View, Text } from 'react-native';
function App() {
return (
<Pulse.ErrorBoundary fallback={<Text>Something went wrong</Text>}>
{/* Your app */}
</Pulse.ErrorBoundary>
);
}
You can provide a custom fallback UI with error details:
function ErrorFallback({ error, componentStack }) {
return (
<View>
<Text>An error occurred</Text>
<Text>{error.toString()}</Text>
</View>
);
}
function App() {
return (
<Pulse.ErrorBoundary fallback={ErrorFallback}>
{/* Your app */}
</Pulse.ErrorBoundary>
);
}
Use the onError callback for additional error handling logic:
<Pulse.ErrorBoundary
fallback={ErrorFallback}
onError={(error, componentStack) => {
// Custom logging or side effects
console.log('Render error:', error);
console.log('Component stack:', componentStack);
}}
>
{/* Your app */}
</Pulse.ErrorBoundary>
Wrap individual components using the HOC pattern:
import { Pulse } from '@horizoneng/pulse-react-native';
const MyComponent = () => {
// Component code
};
export default Pulse.withErrorBoundary(MyComponent, {
fallback: <Text>Error occurred</Text>,
onError: (error, componentStack) => {
console.log('Component error:', error);
}
});
How it works:
fallback is provided, the error is marked as handled (non-fatal)fallback is provided, the error is marked as unhandled (fatal)Note: In development mode, React will rethrow errors caught by error boundaries. This may result in errors being reported twice. We recommend testing error boundaries with production builds.
ANR (Application Not Responding) and frozen frame detection are automatically handled by the native Android SDK.
What's detected:
Configuration:
ANR and frozen frame detection are enabled by default. To customize thresholds, detection intervals, or disable specific checks, see the Android SDK documentation:
If your app uses over-the-air (OTA) updates like Microsoft App Center CodePush or Delivr DOTA, you can track which JavaScript bundle version is running by setting a global attribute.
With OTA updates, the same native app version can run multiple different JavaScript bundles. Tracking the code bundle ID helps you:
Use Pulse.setGlobalAttribute() to pass the code bundle ID from your OTA provider:
import codePush from '@d11/dota';
import { Pulse } from '@horizoneng/pulse-react-native';
// Set on app start
codePush.getUpdateMetadata().then(update => {
if (update?.label) {
Pulse.setGlobalAttribute('codeBundleId', update.label);
}
});
All subsequent events, errors, and spans will automatically include the codeBundleId attribute, allowing you to filter and analyze telemetry data by deployment version.
Tip: For best results, set the code bundle ID before calling
Pulse.start()to ensure all telemetry includes this identifier.
Global attributes are automatically attached to all telemetry data (events, errors, spans) throughout your application's lifecycle.
Note: Global attributes set via
setGlobalAttributeonly apply to telemetry originating from the React Native side. Native Android events (ANR, frozen frames, activity lifecycle, etc.) are not affected. To set global attributes for native Android telemetry, refer to the Pulse Android SDK initialization guide.
import { Pulse } from '@horizoneng/pulse-react-native';
// Set global attributes
Pulse.setGlobalAttribute('environment', 'production');
Pulse.setGlobalAttribute('buildNumber', '1234');
// All subsequent telemetry will include these attributes
Pulse.trackEvent('user_login', { userId: '123' });
import DeviceInfo from 'react-native-device-info';
import { Pulse } from '@horizoneng/pulse-react-native';
// Set once at app start
Pulse.setGlobalAttribute('appVersion', DeviceInfo.getVersion());
Pulse.setGlobalAttribute('environment', __DEV__ ? 'development' : 'production');
Pulse.setGlobalAttribute('userTier', 'premium');
Event/span-specific attributes override global attributes when keys conflict:
Pulse.setGlobalAttribute('environment', 'production');
// This event's 'environment' will be 'staging'
Pulse.trackEvent('test', { environment: 'staging' });
Common use cases: CodePush labels, build numbers, environment flags, feature flags, user segments, device metadata.
Associate telemetry with specific users:
// Set user ID
Pulse.setUserId('user-12345');
// Set individual properties
Pulse.setUserProperty('email', 'user@example.com');
Pulse.setUserProperty('plan', 'premium');
// Set multiple properties at once
Pulse.setUserProperties({
email: 'user@example.com',
plan: 'premium',
signupDate: '2024-01-15',
verified: true
});
// Clear user on logout
Pulse.setUserId(null);
Create custom performance traces (spans) to measure the execution time of specific operations in your application. Spans help you understand which parts of your code are slow and need optimization.
A span represents a unit of work or operation. Each span has:
'screen_render', 'image_upload', 'payment_flow'){ screenName: 'Checkout', itemCount: 3 })'validation_passed', 'api_call_started', 'ui_rendered')When you start a span, it becomes active, meaning it's the current span being tracked. Any errors that occur while a span is active are automatically associated with that span.
trackSpan() (Recommended)The trackSpan() function automatically manages the span lifecycle for you. It starts the span, executes your code, and ends the span when done—even if an error occurs. This works with both synchronous and asynchronous operations.
Synchronous operation:
import { Pulse } from '@horizoneng/pulse-react-native';
const result = Pulse.trackSpan('calculate_total',
{ attributes: { itemCount: 5 } },
() => {
let total = 0;
for (let i = 0; i < 1000; i++) {
total += i;
}
return total;
}
);
Asynchronous operation:
const users = await Pulse.trackSpan('fetch_users',
{ attributes: { limit: 100 } },
async () => {
const response = await fetch('https://api.example.com/users?limit=100');
return response.json();
}
);
startSpan()Use startSpan() when you need fine-grained control over the span lifecycle. This is useful for:
Basic example:
const span = Pulse.startSpan('image_processing', {
attributes: { imageId: 'img-123', format: 'jpeg' }
});
try {
span.addEvent('resize_started');
await resizeImage();
span.addEvent('resize_completed', { newSize: '800x600' });
span.addEvent('compression_started');
await compressImage();
span.addEvent('compression_completed');
span.setAttributes({ finalSize: 245000, compressionRatio: 0.65 });
} catch (error) {
span.recordException(error);
span.setAttributes({ status: 'failed' });
} finally {
span.end(); // Always end the span
}
Span API:
import { Pulse, SpanStatusCode } from '@horizoneng/pulse-react-native';
// Add a single event
span.addEvent('cache_miss');
// Add event with attributes
span.addEvent('retry_attempt', { attemptNumber: 2, delayMs: 1000 });
// Update span attributes
span.setAttributes({
recordsProcessed: 150,
cacheHitRate: 0.85
});
// Record an exception
try {
await riskyOperation();
} catch (error) {
span.recordException(error);
span.end(SpanStatusCode.ERROR); // Mark span as failed
}
// End the span with status
span.end(); // Default: SpanStatusCode.UNSET
span.end(SpanStatusCode.OK); // Explicitly mark as successful
span.end(SpanStatusCode.ERROR); // Mark as failed
You can optionally set a status code when ending a span to indicate the outcome:
| Status | Description | When to Use |
|---|---|---|
SpanStatusCode.OK | Operation completed successfully | Successful operations, no errors |
SpanStatusCode.ERROR | Operation failed or encountered an error | Failures, exceptions, validation errors |
SpanStatusCode.UNSET | Status not specified (default) | When outcome is unknown or not applicable |
Example with status codes:
import { Pulse, SpanStatusCode } from '@horizoneng/pulse-react-native';
const span = Pulse.startSpan('payment_processing');
try {
await processPayment();
span.end(SpanStatusCode.OK); // Success
} catch (error) {
span.recordException(error);
span.end(SpanStatusCode.ERROR); // Failure
}
'fetch_user_profile' is better than 'api_call'try/finally to ensure span.end() is calledspan.recordException(error) to capture errors'cache_hit', 'retry_started', 'validation_passed'Note: Spans created with
startSpan()must be manually ended withspan.end(). Forgetting to end a span will result in incomplete telemetry data.
Track custom business events, user actions, and application milestones to gain insights into user behavior and application usage patterns.
import { Pulse } from '@horizoneng/pulse-react-native';
// Simple event without attributes
Pulse.trackEvent('app_opened');
Pulse.trackEvent('user_logout');
Add context to your events with attributes:
// E-commerce events
Pulse.trackEvent('product_viewed', {
productId: 'SKU-12345',
category: 'electronics',
price: 299.99,
inStock: true
});
| Method | Parameters | Returns | Description |
|---|---|---|---|
Pulse.start(options?) | options?: PulseStartOptions | void | Initialize auto-instrumentation. All options default to true. |
Pulse.isInitialized() | - | boolean | Check if native SDK is initialized. |
PulseStartOptions:
{
autoDetectExceptions?: boolean; // Auto-detect JS crashes & errors
autoDetectNavigation?: boolean; // Auto-detect navigation
autoDetectNetwork?: boolean; // Auto-detect HTTP requests
}
| Method | Parameters | Returns | Description |
|---|---|---|---|
Pulse.reportException(error, isFatal?, attributes?) | error: Error | stringisFatal?: booleanattributes?: PulseAttributes | void | Report an exception. Default isFatal: false. |
| Method | Parameters | Returns | Description |
|---|---|---|---|
Pulse.trackEvent(event, attributes?) | event: stringattributes?: PulseAttributes | void | Track a custom event with optional attributes. |
| Method | Parameters | Returns | Description |
|---|---|---|---|
Pulse.trackSpan(name, options, fn) | name: stringoptions: { attributes?: PulseAttributes }fn: () => T | Promise<T> | T | Promise<T> | Auto-managed span. Returns function result. |
Pulse.startSpan(name, options?) | name: stringoptions?: { attributes?: PulseAttributes } | Span | Create a span with manual control. |
Span Interface:
interface Span {
spanId: string;
end(statusCode?: SpanStatusCode): void;
addEvent(name: string, attributes?: PulseAttributes): void;
setAttributes(attributes?: PulseAttributes): void;
recordException(error: Error, attributes?: PulseAttributes): void;
}
// Span status codes
const SpanStatusCode = {
OK: 'OK',
ERROR: 'ERROR',
UNSET: 'UNSET',
} as const;
type SpanStatusCode = typeof SpanStatusCode[keyof typeof SpanStatusCode];
| Method | Parameters | Returns | Description |
|---|---|---|---|
Pulse.setUserId(id) | id: string | null | void | Set user ID. Pass null to clear. |
Pulse.setUserProperty(name, value) | name: stringvalue: string | null | void | Set a single user property. |
Pulse.setUserProperties(properties) | properties: PulseAttributes | void | Set multiple user properties. |
| Method | Parameters | Returns | Description |
|---|---|---|---|
Pulse.setGlobalAttribute(key, value) | key: stringvalue: string | void | Set attribute for all events/spans. Pass empty string to remove. |
| Component/HOC | Props | Description |
|---|---|---|
<Pulse.ErrorBoundary> | fallback?: React.ReactElement | FallbackRenderonError?: (error, componentStack) => void | Catch React render errors. |
Pulse.withErrorBoundary(Component, options) | Component: React.ComponentTypeoptions: ErrorBoundaryProps | HOC to wrap component with error boundary. |
// Attribute values - primitives and homogeneous arrays
type PulseAttributeValue =
| string
| number
| boolean
| Array<null | undefined | string>
| Array<null | undefined | number>
| Array<null | undefined | boolean>;
// Attributes object
type PulseAttributes = Record<string, PulseAttributeValue | undefined>;
Symptoms:
JavaScript console:
⚠️ [Pulse RN] Events will not be sent - SDK not initialized.
Call Pulse.start() and initialize PulseSDK in MainApplication.kt
Android logcat:
W/PulseLogger: PulseSDK not initialized. Events will not be tracked...
Cause: Native Android SDK not initialized.
Solution:
MainApplication.kt:override fun onCreate() {
super.onCreate()
PulseSDK.INSTANCE.initialize(this)
}
console.log('Pulse ready:', Pulse.isInitialized());
cd android && ./gradlew clean
cd .. && npx react-native run-android
Possible causes:
autoInstrumentNetwork: false in Pulse.start()Solution:
Pulse.start({ autoInstrumentNetwork: true });
Possible causes:
autoInstrumentNavigation: false in Pulse.start()Solution:
Pulse.start({ autoInstrumentNavigation: true });
const integration = Pulse.createNavigationIntegration();
<NavigationContainer onReady={integration.registerNavigationContainer}>
Initialize the native SDK as early as possible in your app lifecycle:
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
PulseSDK.INSTANCE.initialize(this) // First thing
// ... other initializations
}
}
Set user information right after authentication:
async function handleLogin(credentials) {
const user = await login(credentials);
// Set user context immediately
Pulse.setUserId(user.id);
Pulse.setUserProperties({
email: user.email,
plan: user.subscription,
signupDate: user.createdAt
});
}
Set global attributes for information that doesn't change during the session:
Pulse.start();
// Set once at app start
Pulse.setGlobalAttribute('appVersion', DeviceInfo.getVersion());
Pulse.setGlobalAttribute('buildType', __DEV__ ? 'debug' : 'release');
Pulse.setGlobalAttribute('deviceModel', DeviceInfo.getModel());
trackSpan() Over startSpan()Use automatic span management unless you need fine-grained control:
// ✅ Preferred - automatic lifecycle
await Pulse.trackSpan('operation', {}, async () => {
await doWork();
});
// ⚠️ Use only when needed - manual lifecycle
const span = Pulse.startSpan('operation');
await doWork();
span.end(); // Easy to forget!
Always include relevant context when reporting errors:
try {
await syncData();
} catch (error) {
Pulse.reportException(error, false, {
operation: 'data_sync',
userId: currentUser?.id,
lastSyncTime: lastSync.toISOString(),
itemsPending: pendingItems.length
});
}
MIT
Made with ❤️ using create-react-native-library
FAQs
Production-grade observability SDK for React Native applications with OpenTelemetry support. Real-time monitoring, error tracking, performance insights, and distributed tracing.
We found that @horizoneng/pulse-react-native demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.

Security News
Open source attacks are accelerating as AI coding agents pull in dependencies faster, with less human review.

Research
/Security News
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.