Universal event tracking SDK for Logg Signals
Universal event tracking SDK for Logg Signals. Track events from web, React Native, and Node.js applications.
Version 0.1.4 - Test release
✅ Universal - Works in browsers, React Native, and Node.js
✅ Type-safe - Full TypeScript support
✅ Automatic batching - Efficient event batching with configurable thresholds
✅ Persistent storage - Uses localStorage, AsyncStorage, or memory as fallback
✅ Retry logic - Exponential backoff for failed requests
✅ Auto metadata - Automatically collects browser/device information
✅ Small bundle - <5KB gzipped
``bash`
npm install @logg/signals
For React Native, also install AsyncStorage:
`bash`
npm install @react-native-async-storage/async-storage
`typescript
import { Signals } from '@logg/signals';
const signals = new Signals({
apiKey: 'your-api-key',
endpoint: 'https://signals.yourdomain.com/events',
});
// Track events
signals.event({
type: 'page_view',
page: '/dashboard',
userId: '12345',
});
signals.event({
type: 'button_click',
element: 'signup_cta',
userId: '12345',
});
`
`typescript
import { Signals } from '@logg/signals';
import AsyncStorage from '@react-native-async-storage/async-storage';
const signals = new Signals({
apiKey: 'your-api-key',
endpoint: 'https://signals.yourdomain.com/events',
// AsyncStorage is auto-detected, but you can pass it explicitly
});
// Track events
signals.event({
type: 'screen_view',
screen: 'HomeScreen',
userId: user.id,
});
signals.event({
type: 'purchase',
productId: 'premium-plan',
amount: 29.99,
userId: user.id,
});
`
`typescript
import { Signals } from '@logg/signals';
const signals = new Signals({
apiKey: 'your-api-key',
endpoint: 'https://signals.yourdomain.com/events',
});
// Track server-side events
await signals.event({
type: 'api_call',
endpoint: '/api/users',
method: 'POST',
userId: req.user.id,
});
// Make sure to flush before process exit
process.on('beforeExit', async () => {
await signals.flush();
});
`
`typescript
const signals = new Signals({
// Required
apiKey: 'your-api-key',
endpoint: 'https://signals.yourdomain.com/events',
// Optional
batchSize: 10, // Send after 10 events (default: 10)
batchInterval: 5000, // Or every 5 seconds (default: 5000)
maxRetries: 3, // Retry failed requests 3 times (default: 3)
retryDelay: 1000, // Initial retry delay in ms (default: 1000)
debug: false, // Enable debug logging (default: false)
sessionId: 'custom-id', // Custom session ID (auto-generated by default)
storage: customAdapter, // Custom storage adapter (auto-detected by default)
});
`
Track an event. Events are automatically batched and sent based on batchSize and batchInterval config.
`typescript`
await signals.event({
type: 'event_type', // Required: event type
userId: 'user-123', // Optional: user ID
// ... any other properties
});
Auto-added fields:
- event_id - Unique event identifier (UUID v4)timestamp
- - ISO 8601 timestampsession_id
- - Session identifierclient
- - Client metadata (type, version, user_agent, screen, locale, timezone)
Manually flush all pending events immediately.
`typescript`
await signals.flush();
Get the current session ID.
`typescript`
const sessionId = signals.getSessionId();
Get the number of events in the queue.
`typescript`
const queueSize = signals.getQueueSize();
Destroy the client, flush remaining events, and cleanup resources.
`typescript`
await signals.destroy();
Events are automatically batched to reduce network requests:
1. Batch by size: Sends when batchSize events are queued (default: 10)batchInterval
2. Batch by time: Sends every milliseconds (default: 5000)signals.flush()
3. Manual flush: Call to send immediately
Batch format sent to backend:
`json`
{
"api_key": "your-api-key",
"batch_id": "batch-uuid",
"timestamp": "2025-12-02T10:30:00.000Z",
"metadata": {
"type": "web",
"version": "0.1.0",
"user_agent": "Mozilla/5.0...",
"screen": { "width": 1920, "height": 1080 },
"locale": "en-US",
"timezone": "America/New_York"
},
"events": [
{
"event_id": "uuid-1",
"timestamp": "2025-12-02T10:30:00.000Z",
"session_id": "session-uuid",
"type": "page_view",
"userId": "12345",
"page": "/dashboard"
}
]
}
The SDK automatically detects the best storage adapter:
1. Web: LocalStorageAdapter (uses localStorage)AsyncStorageAdapter
2. React Native: (uses @react-native-async-storage/async-storage)MemoryStorageAdapter
3. Node.js: (in-memory, no persistence)
You can provide a custom storage adapter:
`typescript
import { Signals, StorageAdapter } from '@logg/signals';
class CustomStorageAdapter implements StorageAdapter {
async getItem(key: string): Promise
// Your implementation
}
async setItem(key: string, value: string): Promise
// Your implementation
}
async removeItem(key: string): Promise
// Your implementation
}
}
const signals = new Signals({
apiKey: 'your-api-key',
endpoint: 'https://signals.yourdomain.com/events',
storage: new CustomStorageAdapter(),
});
`
The SDK includes automatic retry logic with exponential backoff:
- Failed requests are retried up to maxRetries times (default: 3)
- Retry delay doubles after each attempt (exponential backoff)
- Events are persisted in storage and retried on next batch
`typescript`
const signals = new Signals({
apiKey: 'your-api-key',
endpoint: 'https://signals.yourdomain.com/events',
maxRetries: 5, // Retry up to 5 times
retryDelay: 2000, // Start with 2 second delay
debug: true, // Log retry attempts
});
`typescript
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function App() {
const location = useLocation();
useEffect(() => {
signals.event({
type: 'page_view',
page: location.pathname,
title: document.title,
});
}, [location]);
return
$3
`typescript
function SignupButton() {
const handleClick = () => {
signals.event({
type: 'button_click',
element: 'signup_cta',
page: '/landing',
}); // Navigate to signup...
};
return ;
}
`React Native Integration
`typescript
import { Signals } from '@logg/signals';
import { useEffect } from 'react';
import { useNavigation } from '@react-navigation/native';const signals = new Signals({
apiKey: 'your-api-key',
endpoint: 'https://signals.yourdomain.com/events',
});
function HomeScreen() {
const navigation = useNavigation();
useEffect(() => {
// Track screen view
signals.event({
type: 'screen_view',
screen: 'HomeScreen',
});
}, []);
return (
title="Buy Premium"
onPress={() => {
signals.event({
type: 'button_press',
button: 'buy_premium',
screen: 'HomeScreen',
});
navigation.navigate('Checkout');
}}
/>
);
}
`Best Practices
$3
Create a single instance and reuse it:
`typescript
// lib/signals.ts
import { Signals } from '@logg/signals';export const signals = new Signals({
apiKey: process.env.SIGNALS_API_KEY!,
endpoint: process.env.SIGNALS_ENDPOINT!,
});
// app.tsx
import { signals } from './lib/signals';
signals.event({ type: 'app_opened' });
`$3
Always flush events before the app closes:
`typescript
// React Native
useEffect(() => {
return () => {
signals.flush();
};
}, []);// Node.js
process.on('beforeExit', async () => {
await signals.flush();
});
`$3
Define your event types for better DX:
`typescript
type AppEvent =
| { type: 'page_view'; page: string; title: string }
| { type: 'button_click'; element: string }
| { type: 'purchase'; productId: string; amount: number };const signals = new Signals({...});
function trackEvent(event: AppEvent) {
signals.event(event);
}
// Now fully type-safe!
trackEvent({ type: 'page_view', page: '/home', title: 'Home' });
`$3
Include user ID in all events:
`typescript
function trackUserEvent(event: Omit) {
const userId = getCurrentUserId();
signals.event({ ...event, userId });
}
``MIT