npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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/client

Quick 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): void

Events 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): void

Attribution 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): void
layers.setUserProperties({
  email: '[email protected]',
  plan: 'premium',
  signup_date: '2024-01-15'
});

Consent Management

setConsent(consent: ConsentState): void
getConsentState(): ConsentState
interface 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(): void

Device Info

setDeviceInfo(deviceInfo: DeviceContext): void

Override 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: 3000ms

Error Handling

on(event: 'error', listener: (error: Error) => void): this
off(event: 'error', listener: (error: Error) => void): this
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

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

// 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';