@logg/signals
v0.1.1
Published
Universal event tracking SDK for Logg Signals
Maintainers
Readme
@logg/signals
Universal event tracking SDK for Logg Signals. Track events from web, React Native, and Node.js applications.
Features
✅ 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
Installation
npm install @logg/signalsFor React Native, also install AsyncStorage:
npm install @react-native-async-storage/async-storageQuick Start
Web (Browser)
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',
});React Native
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,
});Node.js
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();
});Configuration
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)
});API Reference
signals.event(eventData)
Track an event. Events are automatically batched and sent based on batchSize and batchInterval config.
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)
signals.flush()
Manually flush all pending events immediately.
await signals.flush();signals.getSessionId()
Get the current session ID.
const sessionId = signals.getSessionId();signals.getQueueSize()
Get the number of events in the queue.
const queueSize = signals.getQueueSize();signals.destroy()
Destroy the client, flush remaining events, and cleanup resources.
await signals.destroy();Event Batching
Events are automatically batched to reduce network requests:
- Batch by size: Sends when
batchSizeevents are queued (default: 10) - Batch by time: Sends every
batchIntervalmilliseconds (default: 5000) - Manual flush: Call
signals.flush()to send immediately
Batch format sent to backend:
{
"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"
}
]
}Storage Adapters
The SDK automatically detects the best storage adapter:
- Web:
LocalStorageAdapter(useslocalStorage) - React Native:
AsyncStorageAdapter(uses@react-native-async-storage/async-storage) - Node.js:
MemoryStorageAdapter(in-memory, no persistence)
Custom Storage Adapter
You can provide a custom storage adapter:
import { Signals, StorageAdapter } from '@logg/signals';
class CustomStorageAdapter implements StorageAdapter {
async getItem(key: string): Promise<string | null> {
// Your implementation
}
async setItem(key: string, value: string): Promise<void> {
// Your implementation
}
async removeItem(key: string): Promise<void> {
// Your implementation
}
}
const signals = new Signals({
apiKey: 'your-api-key',
endpoint: 'https://signals.yourdomain.com/events',
storage: new CustomStorageAdapter(),
});Error Handling
The SDK includes automatic retry logic with exponential backoff:
- Failed requests are retried up to
maxRetriestimes (default: 3) - Retry delay doubles after each attempt (exponential backoff)
- Events are persisted in storage and retried on next batch
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
});React Integration
Track Page Views
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 <div>...</div>;
}Track User Actions
function SignupButton() {
const handleClick = () => {
signals.event({
type: 'button_click',
element: 'signup_cta',
page: '/landing',
});
// Navigate to signup...
};
return <button onClick={handleClick}>Sign Up</button>;
}React Native Integration
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 (
<Button
title="Buy Premium"
onPress={() => {
signals.event({
type: 'button_press',
button: 'buy_premium',
screen: 'HomeScreen',
});
navigation.navigate('Checkout');
}}
/>
);
}Best Practices
1. Initialize Once
Create a single instance and reuse it:
// 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' });2. Flush on Exit
Always flush events before the app closes:
// React Native
useEffect(() => {
return () => {
signals.flush();
};
}, []);
// Node.js
process.on('beforeExit', async () => {
await signals.flush();
});3. Type-safe Events
Define your event types for better DX:
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' });4. User Identification
Include user ID in all events:
function trackUserEvent(event: Omit<EventData, 'userId'>) {
const userId = getCurrentUserId();
signals.event({ ...event, userId });
}License
MIT
