@layers/client
v2.1.1
Published
Layers Analytics Web SDK — thin wrapper over Rust core via WASM
Readme
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
npm install @layers/client
# or
yarn add @layers/client
# or
pnpm add @layers/clientQuick Start
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
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. |
const layers = new LayersClient({
appId: 'your-app-id',
environment: 'development',
enableDebug: true,
flushIntervalMs: 15000,
flushThreshold: 5,
maxQueueSize: 5000
});Core API
Constructor & Initialization
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
track(eventName: string, properties?: EventProperties): voidEvents are batched and flushed automatically. Attribution properties (UTM, click IDs, referrer) from the current session are automatically merged into every event.
layers.track('purchase_completed', {
product_id: 'sku_123',
price: 9.99,
currency: 'USD'
});Screen/Page Tracking
screen(screenName: string, properties?: EventProperties): voidAttribution properties are automatically merged.
layers.screen('ProductDetail', { product_id: 'sku_123' });User Identity
// 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// After login
layers.setAppUserId('user_123');
// On logout
layers.setAppUserId(undefined);User Properties
setUserProperties(properties: UserProperties): voidlayers.setUserProperties({
email: '[email protected]',
plan: 'premium',
signup_date: '2024-01-15'
});Consent Management
setConsent(consent: ConsentState): void
getConsentState(): ConsentStateinterface ConsentState {
analytics?: boolean;
advertising?: boolean;
}// User accepts analytics only
layers.setConsent({ analytics: true, advertising: false });
// Read current state
const consent = layers.getConsentState();Session Management
// Get the current session ID
getSessionId(): string
// End the current session and start a new one
startNewSession(): voidDevice Info
setDeviceInfo(deviceInfo: DeviceContext): voidOverride auto-detected device context fields.
Flush & Shutdown
// 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: 3000msError Handling
on(event: 'error', listener: (error: Error) => void): this
off(event: 'error', listener: (error: Error) => void): thislayers.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
- On
init(), the SDK reads the current URL's query parameters anddocument.referrer. - Attribution data is persisted in
localStoragewith a 30-day TTL. - Click IDs take priority -- a new click ID overwrites the entire stored record.
- UTM parameters overwrite on fresh campaign visits, but preserve existing click IDs.
- Attribution properties are automatically prefixed with
$attribution_and merged into everytrack()andscreen()call.
Manually Reading Attribution
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
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
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
function useLayers(): LayersClient;Returns the LayersClient instance. Throws if used outside a <LayersProvider>.
function MyComponent() {
const layers = useLayers();
layers.track('component_viewed');
}useTrack
function useTrack(): (eventName: string, properties?: EventProperties) => void;Returns a stable, memoized track function.
function SignupButton() {
const track = useTrack();
return <button onClick={() => track('signup_click', { source: 'hero' })}>Sign Up</button>;
}useScreen
function useScreen(): (screenName: string, properties?: EventProperties) => void;Returns a stable, memoized screen tracking function.
function Dashboard() {
const screen = useScreen();
useEffect(() => {
screen('Dashboard');
}, [screen]);
return <div>Dashboard</div>;
}useIdentify
function useIdentify(): (userId: string | undefined) => void;Returns a stable, memoized identify function. Pass undefined to clear.
function LoginForm() {
const identify = useIdentify();
const onLogin = (userId: string) => identify(userId);
const onLogout = () => identify(undefined);
}useConsent
function useConsent(): {
setConsent: (consent: ConsentState) => void;
getConsentState: () => ConsentState;
};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()andscreen()call. - Online/offline detection: Events are flushed when the browser reconnects.
- Visibility change flush: Events are flushed via
navigator.sendBeaconwhen the page is hidden (tab switch, minimize). - Before unload flush: Events are persisted to
localStorageon 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
localStorageand rehydrated on page load.
SPA (Single Page App) Usage
For single-page apps with client-side routing, call screen() on route changes:
// 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;
}'use client';
import { usePathname } from 'next/navigation';
function PageTracker() {
const pathname = usePathname();
const screen = useScreen();
useEffect(() => {
screen(pathname);
}, [pathname, screen]);
return null;
}TypeScript Types
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:
import type {
BaseEvent,
ConsentPayload,
EventsBatchPayload,
RemoteConfigResponse,
SKANPostbackPayload,
UserPropertiesPayload
} from '@layers/client';