npm package: @layers/client
Description: Layers Analytics Web SDK — thin wrapper over Rust core via WASM
Author: Layers Team
Latest version: 2.2.0

# Layers Web SDK

`@layers/client` is the Layers analytics SDK for web browsers. It provides event tracking, screen tracking, user identification, automatic attribution capture (UTM parameters, click IDs, referrer), consent management, and lifecycle-aware event flushing with `sendBeacon` support.

## Requirements

- Modern browser with ES module support
- React 18+ (for React hooks, optional)

## Installation

```bash
npm install @layers/client
# or
yarn add @layers/client
# or
pnpm add @layers/client
```

## Quick Start

```typescript
import { LayersClient } from '@layers/client';

const layers = new LayersClient({
  appId: 'your-app-id',
  environment: 'production'
});
await layers.init();

// Track events
layers.track('button_click', { button_name: 'signup' });

// Track page views
layers.screen('Home');

// Identify users
layers.setAppUserId('user_123');
```

## Configuration

### LayersConfig

```typescript
interface LayersConfig {
  appId: string;
  environment: 'development' | 'staging' | 'production';
  appUserId?: string;
  enableDebug?: boolean;
  baseUrl?: string;
  flushIntervalMs?: number;
  flushThreshold?: number;
  maxQueueSize?: number;
}
```

| Option            | Type          | Default                   | Description                                      |
| ----------------- | ------------- | ------------------------- | ------------------------------------------------ |
| `appId`           | `string`      | _required_                | Your Layers application identifier.              |
| `environment`     | `Environment` | _required_                | `'development'`, `'staging'`, or `'production'`. |
| `appUserId`       | `string`      | `undefined`               | Optional user ID to set at construction time.    |
| `enableDebug`     | `boolean`     | `false`                   | Enable verbose console logging.                  |
| `baseUrl`         | `string`      | `"https://in.layers.com"` | Custom ingest API endpoint.                      |
| `flushIntervalMs` | `number`      | `30000`                   | Automatic flush interval in milliseconds.        |
| `flushThreshold`  | `number`      | `10`                      | Queue size that triggers an automatic flush.     |
| `maxQueueSize`    | `number`      | `1000`                    | Maximum events in the queue before dropping.     |

```typescript
const layers = new LayersClient({
  appId: 'your-app-id',
  environment: 'development',
  enableDebug: true,
  flushIntervalMs: 15000,
  flushThreshold: 5,
  maxQueueSize: 5000
});
```

## Core API

### Constructor & Initialization

```typescript
const layers = new LayersClient(config: LayersConfig);
await layers.init();
```

The constructor creates the SDK instance with localStorage-backed persistence. Calling `init()` detects device info, attaches lifecycle listeners (online/offline, visibility change, beforeunload), captures attribution signals, and fetches remote config.

You can call `track()` and `screen()` before `init()` completes -- events are queued.

### Event Tracking

```typescript
track(eventName: string, properties?: EventProperties): void
```

Events are batched and flushed automatically. Attribution properties (UTM, click IDs, referrer) from the current session are automatically merged into every event.

```typescript
layers.track('purchase_completed', {
  product_id: 'sku_123',
  price: 9.99,
  currency: 'USD'
});
```

### Screen/Page Tracking

```typescript
screen(screenName: string, properties?: EventProperties): void
```

Attribution properties are automatically merged.

```typescript
layers.screen('ProductDetail', { product_id: 'sku_123' });
```

### User Identity

```typescript
// Set or clear the app user ID
setAppUserId(appUserId: string | undefined): void

// Get the current user ID
getAppUserId(): string | undefined

// Deprecated aliases
setUserId(userId: string): void
getUserId(): string | undefined
```

```typescript
// After login
layers.setAppUserId('user_123');

// On logout
layers.setAppUserId(undefined);
```

### User Properties

```typescript
setUserProperties(properties: UserProperties): void
```

```typescript
layers.setUserProperties({
  email: 'user@example.com',
  plan: 'premium',
  signup_date: '2024-01-15'
});
```

### Consent Management

```typescript
setConsent(consent: ConsentState): void
getConsentState(): ConsentState
```

```typescript
interface ConsentState {
  analytics?: boolean;
  advertising?: boolean;
}
```

```typescript
// User accepts analytics only
layers.setConsent({ analytics: true, advertising: false });

// Read current state
const consent = layers.getConsentState();
```

### Session Management

```typescript
// Get the current session ID
getSessionId(): string

// End the current session and start a new one
startNewSession(): void
```

### Device Info

```typescript
setDeviceInfo(deviceInfo: DeviceContext): void
```

Override auto-detected device context fields.

### Flush & Shutdown

```typescript
// Flush all queued events to the server
async flush(): Promise<void>

// Shut down immediately (no flush, events persisted to localStorage)
shutdown(): void

// Shut down with a final flush (with timeout)
async shutdownAsync(timeoutMs?: number): Promise<void>  // default: 3000ms
```

### Error Handling

```typescript
on(event: 'error', listener: (error: Error) => void): this
off(event: 'error', listener: (error: Error) => void): this
```

```typescript
layers.on('error', (error) => {
  console.error('Layers error:', error.message);
});
```

## Attribution

The SDK automatically captures and persists attribution signals from the current page URL and referrer.

### Captured Signals

**Click IDs** (highest priority -- overwrites existing attribution):

- `fbclid` (Meta/Facebook)
- `gclid`, `gbraid`, `wbraid` (Google)
- `ttclid` (TikTok)
- `msclkid` (Microsoft/Bing)
- `rclid` (Reddit)

**UTM Parameters**:

- `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`

**Referrer**:

- `document.referrer`

### How It Works

1. On `init()`, the SDK reads the current URL's query parameters and `document.referrer`.
2. Attribution data is persisted in `localStorage` with a 30-day TTL.
3. Click IDs take priority -- a new click ID overwrites the entire stored record.
4. UTM parameters overwrite on fresh campaign visits, but preserve existing click IDs.
5. Attribution properties are automatically prefixed with `$attribution_` and merged into every `track()` and `screen()` call.

### Manually Reading Attribution

```typescript
import { getAttribution, getAttributionProperties } from '@layers/client';

// Get the full stored attribution data (or null if expired/missing)
const attribution = getAttribution();

// Get a flat property bag for merging into events
const props = getAttributionProperties();
// { '$attribution_utm_source': 'google', '$attribution_click_id_param': 'gclid', ... }
```

### AttributionData Type

```typescript
interface AttributionData {
  click_id_param?: string;
  click_id_value?: string;
  utm_source?: string;
  utm_medium?: string;
  utm_campaign?: string;
  utm_content?: string;
  utm_term?: string;
  referrer_url?: string;
  captured_at: number;
}
```

## React Integration

The SDK includes React hooks for idiomatic usage. Import from `@layers/client/react`.

### LayersProvider

```tsx
import { LayersProvider } from '@layers/client/react';

function App() {
  return (
    <LayersProvider
      config={{
        appId: 'your-app-id',
        environment: 'production'
      }}
    >
      <MyApp />
    </LayersProvider>
  );
}
```

Initializes the client on mount and shuts it down on unmount.

### useLayers

```typescript
function useLayers(): LayersClient;
```

Returns the `LayersClient` instance. Throws if used outside a `<LayersProvider>`.

```tsx
function MyComponent() {
  const layers = useLayers();
  layers.track('component_viewed');
}
```

### useTrack

```typescript
function useTrack(): (eventName: string, properties?: EventProperties) => void;
```

Returns a stable, memoized track function.

```tsx
function SignupButton() {
  const track = useTrack();

  return <button onClick={() => track('signup_click', { source: 'hero' })}>Sign Up</button>;
}
```

### useScreen

```typescript
function useScreen(): (screenName: string, properties?: EventProperties) => void;
```

Returns a stable, memoized screen tracking function.

```tsx
function Dashboard() {
  const screen = useScreen();
  useEffect(() => {
    screen('Dashboard');
  }, [screen]);

  return <div>Dashboard</div>;
}
```

### useIdentify

```typescript
function useIdentify(): (userId: string | undefined) => void;
```

Returns a stable, memoized identify function. Pass `undefined` to clear.

```tsx
function LoginForm() {
  const identify = useIdentify();

  const onLogin = (userId: string) => identify(userId);
  const onLogout = () => identify(undefined);
}
```

### useConsent

```typescript
function useConsent(): {
  setConsent: (consent: ConsentState) => void;
  getConsentState: () => ConsentState;
};
```

```tsx
function ConsentBanner() {
  const { setConsent } = useConsent();

  return (
    <button onClick={() => setConsent({ analytics: true, advertising: false })}>
      Accept Analytics Only
    </button>
  );
}
```

## Automatic Behaviors

- **Attribution capture**: UTM parameters, click IDs, and referrer are captured and persisted on init.
- **Attribution enrichment**: Stored attribution is merged into every `track()` and `screen()` call.
- **Online/offline detection**: Events are flushed when the browser reconnects.
- **Visibility change flush**: Events are flushed via `navigator.sendBeacon` when the page is hidden (tab switch, minimize).
- **Before unload flush**: Events are persisted to `localStorage` on page close.
- **Periodic flush**: Events are flushed on a timer (configurable).
- **Remote config**: Server configuration is fetched during init.
- **Device context**: OS, browser, locale, screen size, and timezone are detected automatically.
- **Event persistence**: Events are persisted in `localStorage` and rehydrated on page load.

## SPA (Single Page App) Usage

For single-page apps with client-side routing, call `screen()` on route changes:

```typescript
// React Router
import { useLocation } from 'react-router-dom';

function RouteTracker() {
  const location = useLocation();
  const screen = useScreen();

  useEffect(() => {
    screen(location.pathname, { search: location.search });
  }, [location, screen]);

  return null;
}
```

```typescript
'use client';

import { usePathname } from 'next/navigation';

function PageTracker() {
  const pathname = usePathname();
  const screen = useScreen();

  useEffect(() => {
    screen(pathname);
  }, [pathname, screen]);

  return null;
}
```

## TypeScript Types

```typescript
import type {
  AttributionData,
  BaseEvent,
  ConsentState,
  DeviceContext,
  Environment,
  ErrorListener,
  EventProperties,
  EventsBatchPayload,
  LayersConfig,
  RemoteConfigResponse,
  UserProperties
} from '@layers/client';
```

## Wire Protocol Types

The package also exports TypeScript interfaces for the Layers wire protocol, useful for building custom integrations:

```typescript
import type {
  BaseEvent,
  ConsentPayload,
  EventsBatchPayload,
  RemoteConfigResponse,
  SKANPostbackPayload,
  UserPropertiesPayload
} from '@layers/client';
```