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

@slnka/web

v1.0.0-beta.18

Published

SLNKA Web SDK — Client-side analytics, deep linking, and attribution (CNDP compliant)

Readme

@slnka/web

Lightweight client-side event tracking SDK for SLNK.

  • Zero production dependencies - everything hand-rolled
  • < 15 KB gzip - smaller than Mixpanel (36 KB), Amplitude (37 KB), Segment (25 KB)
  • GDPR + CNDP compliant by default - no tracking without explicit consent
  • Offline resilient - localStorage queue for failed sends
  • TypeScript first - full type exports, tree-shakeable ESM

Installation

npm / yarn / pnpm

npm install @slnka/web
# or
yarn add @slnka/web
# or
pnpm add @slnka/web

No authentication or registry config needed — @slnka/web is published on the public npm registry.

CDN (script tag)

<script src="https://cdn.jsdelivr.net/npm/@slnka/web/dist/slnka.umd.js"></script>
<script>
  slnka.configure('lsk_live_your_api_key', {
    serverUrl: 'https://api.slnk.ma',
    workspaceId: 'ws_abc123',
  });
</script>

Quick Start

import slnka from '@slnka/web';

// 1. Configure
slnka.configure('lsk_live_your_api_key', {
  serverUrl: 'https://api.slnk.ma',
  workspaceId: 'ws_abc123',
});

// 2. Grant consent (required by default — privacy-first mode)
slnka.optIn();

// 3. Track events
slnka.track('button_click', { buttonId: 'cta-hero' });
slnka.page();
slnka.identify('user_123', { plan: 'pro' });

API Reference

slnka.configure(apiKey, config)

Configures the SDK. Must be called before any other method.

slnka.configure('lsk_live_your_api_key', {
  serverUrl: 'https://your-instance.slnk.ma',  // Required
  workspaceId: 'ws_abc123',
  flushInterval: 5000,
  flushSize: 10,
  persistence: 'cookie',
  respectDnt: true,
  respectGpc: true,
  requireConsent: true,
  queueBeforeConsent: true,
  debug: false,
  cookieExpiry: 365,
  sessionTimeout: 30,
});

The apiKey can optionally embed the workspace ID: 'lsk_live_abc123:ws_def456'.

slnka.track(eventName, properties?)

Tracks a custom event.

slnka.track('purchase_completed', {
  product: 'Pro Plan',
  amount: 49.99,
  currency: 'MAD',
});

Each event is automatically enriched with:

  • eventId (UUIDv4)
  • anonymousId
  • userId (if identified)
  • sessionId
  • timestamp (ISO 8601 UTC)
  • context (page URL, referrer, user agent, screen, locale, timezone)

slnka.page(properties?)

Tracks a page view ($page_view event). Page URL, path, title, and referrer are auto-collected.

slnka.page({ section: 'pricing' });

slnka.identify(userId, traits?)

Links the current anonymous ID to a known user. Sends a $identify event.

slnka.identify('user_123', {
  email: '[email protected]',
  plan: 'professional',
  company: 'Acme Corp',
});

slnka.reset()

Generates a new anonymous ID, clears the user ID, and starts a fresh session. Use on logout.

slnka.reset();

slnka.flush()

Manually flushes all queued events. Returns a Promise.

await slnka.flush();

slnka.getAnonymousId()

Returns the current anonymous ID.

slnka.getUserId()

Returns the current user ID (or null if not identified).

slnka.setDebug(enabled)

Enables or disables debug logging to the browser console.

slnka.setDebug(true); // See all SDK activity in console

Consent Management

The SDK is GDPR + CNDP compliant by default: no cookies are written and no events are sent until the user explicitly grants consent.

slnka.optIn(category?)

Grants consent. Without a category argument, grants all categories.

// Grant all categories
slnka.optIn();

// Grant a specific category
slnka.optIn('analytics');

When analytics consent is granted, any events tracked before consent (the "pre-consent queue") are automatically flushed.

slnka.optOut(category?)

Revokes consent. Purges the event queue and deletes all SDK cookies/storage.

slnka.optOut();

slnka.getConsentState()

Returns the current consent state for all categories.

const state = slnka.getConsentState();
// { analytics: 'granted', marketing: 'pending', functional: 'granted' }

Consent Categories

| Category | Purpose | |----------|---------| | analytics | Event tracking, page views, user behavior | | marketing | Campaign attribution, cross-site tracking | | functional | Feature flags, A/B testing, personalization |

Consent States

| State | Behavior | |-------|----------| | pending | Events queued in memory (not sent, no cookies) | | granted | Events sent, cookies written | | denied | Events dropped, cookies/storage purged |

Browser Privacy Signals

The SDK respects:

  • Do Not Track (DNT): navigator.doNotTrack === '1' - consent auto-denied
  • Global Privacy Control (GPC): navigator.globalPrivacyControl === true - consent auto-denied

Disable with respectDnt: false and/or respectGpc: false.


Privacy Compliance (GDPR + CNDP)

SLNK is designed for compliance with EU GDPR (Regulation 2016/679) and Morocco's CNDP Law 09-08 (Commission Nationale de contrôle de la protection des Données à caractère Personnel). The same technical mechanisms satisfy both frameworks:

  1. No tracking without consent - requireConsent: true is the default.
  2. No cookies before consent - Anonymous ID is held in memory until optIn().
  3. Pre-consent queue - Events tracked before consent are buffered in memory (not localStorage) and flushed only after optIn().
  4. Full data purge on opt-out - optOut() deletes all cookies, localStorage entries, and queued events.
  5. DNT and GPC respected - Browser-level privacy signals are honored.
  6. No raw IP storage - IP hashing happens server-side (SHA-256 + daily salt).
  7. First-party cookies only - SameSite=Lax, Secure, on your domain.

Cookie Banner Integration

// Configure SDK (no cookies yet)
slnka.configure('lsk_live_your_api_key', {
  serverUrl: 'https://api.slnk.ma',
  workspaceId: 'ws_abc123',
});

// Events are tracked but queued in memory
slnka.page();
slnka.track('page_scroll', { depth: 50 });

// User clicks "Accept" on your cookie banner
document.getElementById('accept-cookies').addEventListener('click', () => {
  slnka.optIn(); // Flushes all queued events, writes cookies
});

// User clicks "Reject"
document.getElementById('reject-cookies').addEventListener('click', () => {
  slnka.optOut(); // Purges everything
});

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | serverUrl | string | Required | Analytics API base URL (e.g., 'https://api.slnk.ma') | | workspaceId | string | '' | Workspace ID (can embed in apiKey) | | flushInterval | number | 5000 | Auto-flush interval in ms | | flushSize | number | 10 | Max buffer size before auto-flush | | persistence | 'cookie' \| 'localStorage' \| 'memory' | 'cookie' | Where to persist anonymous ID | | respectDnt | boolean | true | Respect Do Not Track header | | respectGpc | boolean | true | Respect Global Privacy Control | | requireConsent | boolean | true | Require explicit consent | | queueBeforeConsent | boolean | true | Queue events before consent | | debug | boolean | false | Console debug logging | | cookieDomain | string | auto-detected | Cookie domain | | cookieExpiry | number | 365 | Cookie expiry in days | | sessionTimeout | number | 30 | Session timeout in minutes |


Event Payload

Every event sent to the backend has this shape:

{
  "eventName": "button_click",
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "anonymousId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "userId": "user_123",
  "properties": {
    "buttonId": "cta-hero"
  },
  "context": {
    "page": {
      "url": "https://example.com/pricing",
      "path": "/pricing",
      "title": "Pricing - Example",
      "referrer": "https://google.com"
    },
    "browser": {
      "userAgent": "Mozilla/5.0 ...",
      "language": "fr-MA"
    },
    "screen": {
      "width": 1920,
      "height": 1080
    },
    "locale": "fr-MA",
    "timezone": "Africa/Casablanca"
  },
  "timestamp": "2026-03-15T14:30:00.000Z",
  "sessionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Events are batched and sent to POST /api/v1/sdk/events/batch.


Advanced Usage

Multiple Instances

import { SlnkaClient } from '@slnka/web';

const tracker1 = new SlnkaClient();
tracker1.configure('lsk_live_project_a', {
  serverUrl: 'https://api.slnk.ma',
  workspaceId: 'ws_a',
});

const tracker2 = new SlnkaClient();
tracker2.configure('lsk_live_project_b', {
  serverUrl: 'https://api.slnk.ma',
  workspaceId: 'ws_b',
});

Server-Side Rendering (SSR)

The SDK gracefully handles SSR environments where window, document, and navigator are unavailable. Context fields will be empty strings and storage falls back to in-memory.

Offline Support

When network requests fail, events are automatically persisted to localStorage (key: __slnka_queue). On the next page load, persisted events are restored and retried.


Storage Keys

| Key | Storage | Purpose | |-----|---------|---------| | __slnka_anon | cookie / localStorage / memory | Anonymous user ID | | __slnka_user | cookie / localStorage / memory | Identified user ID | | __slnka_consent | localStorage | Consent state | | __slnka_session | sessionStorage | Session ID | | __slnka_session_activity | sessionStorage | Last activity timestamp | | __slnka_queue | localStorage | Persisted event queue (offline) |


Browser Support

  • Chrome 80+
  • Firefox 80+
  • Safari 14+
  • Edge 80+
  • Mobile Safari (iOS 14+)
  • Chrome Android 80+

The SDK uses fetch, crypto.getRandomValues, sendBeacon, and Intl.DateTimeFormat, all widely supported in modern browsers.

Clés API et scopes (PH03)

Pour le code mobile et web embarqué, créez une clé de collecte (COLLECT) dans les paramètres du projet. Elle est publiable et accepte uniquement events:write, deeplinks:resolve et feedback:write. Activez les scopes nécessaires à votre intégration. Elle ne donne accès ni aux liens, ni aux rapports, ni à l'administration. Le consentement SDK utilise events:write.

Une clé serveur (SERVER) reste dans un environnement serveur privé. Ses scopes sont explicites : links:read, links:write, links:delete, analytics:read, export, et les trois scopes de collecte. La suppression d'un lien exige links:write et links:delete. Aucun scope ne permet d'administrer les membres, rôles, réglages ou clés API. Les routes /api/v1/sdk/** exigent une clé API ; les utilisateurs du tableau de bord utilisent les routes et permissions RBAC correspondantes.

Les clés existantes restent SERVER. La migration ajoute aux clés non révoquées les trois scopes de collecte afin de préserver les intégrations SDK et remplace tags:read/write par links:read/write. Régénérer une clé conserve sa nature et ses scopes. Une nouvelle clé serveur sans scopes explicites reçoit seulement links:read et links:write.

Friction signals and automatic page views

After analytics consent, enhanced measurement detects unresponsive clicks and quick history returns by default. Configure them independently:

slnka.configure('lsk_live_your_api_key', {
  projectId: 'your-project-id',
  enhancedMeasurement: {
    deadClicks: { enabled: true, thresholdMs: 1000, excludeSelectors: ['.custom-player'] },
    quickBacks: { enabled: true, thresholdMs: 3000 },
    pageViews: false,
  },
});
  • $dead_click: a primary click without modifiers followed by no DOM mutation, URL change, scroll, text selection, focus elsewhere or page departure within the threshold. Inputs, editable elements, labels, media and embedded content are excluded. Add data-slnk-ignore to any element to exclude its subtree. At most one signal per selector per five seconds and twenty per page is sent. The payload contains selector, tag, click coordinates and viewport size; element text is never captured. Pure CSS reactions are not observable and can produce false positives: exclude these controls explicitly. An unrelated DOM update can suppress a signal; this is a heuristic, not proof of failure.
  • $quick_back: a history return to the preceding URL in strictly less than three seconds by default. Supports SPA history traversal and same-origin MPA back/forward navigation, including bfcache restoration. URL identity keeps origin, pathname and query, excluding fragments. __slnka_nav in sessionStorage carries the consented last visit across documents; blocked storage degrades cross-document detection. Consent revocation/reset clears it.
  • pageViews: true calls page() after tracker initialization and when the pathname changes. It defaults to false. Remove overlapping manual page() calls if enabling it. Query-only and fragment-only changes do not add views.

Trackers start only when analytics consent permits collection. Revocation or a return to pending consent cancels timers/subscriptions; granting consent again starts a fresh observation period. No pre-consent interaction is reconstructed. Available from 1.0.0-beta.18.

Server surveys and prompt identity

Server-driven surveys use the SurveyDto contract: string id, an ordered steps list and uppercase promptType values. The SDK converts these to its lowercase UI types. An empty or unsupported step list does not start a survey. Single prompt responses retain the server's promptConfigId and trigger; direct feedback.show() callers may also supply promptConfigId.

Survey starts send surveyId, anonymousId, sessionId and pageUrl, then read responseId. Every submitted answer carries its promptConfigId and surveyResponseId. The UI waits for each response before advancing and for the last answer before requesting completion. Failed requests keep the current step available for retry. A failed completion can be retried without resending the already acknowledged final answer. Closing a survey does not change the callback used by later standalone prompts.

Available from 1.0.0-beta.18, with a backend exposing GET /api/v1/sdk/feedback/surveys/{id} and accepting promptConfigId / surveyResponseId on POST /api/v1/sdk/feedback/responses.