npm package: @logg/signals
Description: Universal event tracking SDK for Logg Signals
Author: Logg
Latest version: 0.1.5

# @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

## 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

```bash
npm install @logg/signals
```

For React Native, also install AsyncStorage:

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

## Quick Start

### Web (Browser)

```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',
});
```

### React Native

```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,
});
```

### Node.js

```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();
});
```

## Configuration

```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)
});
```

## API Reference

### `signals.event(eventData)`

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 timestamp
- `session_id` - Session identifier
- `client` - Client metadata (type, version, user_agent, screen, locale, timezone)

### `signals.flush()`

Manually flush all pending events immediately.

```typescript
await signals.flush();
```

### `signals.getSessionId()`

Get the current session ID.

```typescript
const sessionId = signals.getSessionId();
```

### `signals.getQueueSize()`

Get the number of events in the queue.

```typescript
const queueSize = signals.getQueueSize();
```

### `signals.destroy()`

Destroy the client, flush remaining events, and cleanup resources.

```typescript
await signals.destroy();
```

## Event Batching

Events are automatically batched to reduce network requests:

1. **Batch by size**: Sends when `batchSize` events are queued (default: 10)
2. **Batch by time**: Sends every `batchInterval` milliseconds (default: 5000)
3. **Manual flush**: Call `signals.flush()` 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"
    }
  ]
}
```

## Storage Adapters

The SDK automatically detects the best storage adapter:

1. **Web**: `LocalStorageAdapter` (uses `localStorage`)
2. **React Native**: `AsyncStorageAdapter` (uses `@react-native-async-storage/async-storage`)
3. **Node.js**: `MemoryStorageAdapter` (in-memory, no persistence)

### Custom Storage Adapter

You can provide a custom storage adapter:

```typescript
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 `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
});
```

## React Integration

### Track Page Views

```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 <div>...</div>;
}
```

### Track User Actions

```typescript
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

```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 (
    <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:

```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' });
```

### 2. Flush on Exit

Always flush events before the app closes:

```typescript
// 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:

```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' });
```

### 4. User Identification

Include user ID in all events:

```typescript
function trackUserEvent(event: Omit<EventData, 'userId'>) {
  const userId = getCurrentUserId();
  signals.event({ ...event, userId });
}
```

## License

MIT