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

@crelora/mark

v0.2.1

Published

Mark is Crelora's lightweight attribution SDK for capturing user journeys, conversions, and consent across browsers and server-side runtimes. The npm package includes both browser and Node entry points so you can send consistent data from any surface.

Downloads

303

Readme

Crelora Mark SDK

Mark is Crelora's lightweight attribution SDK for capturing user journeys, conversions, and consent across browsers and server-side runtimes. The npm package includes both browser and Node entry points so you can send consistent data from any surface.

Use it to feed OneLence with first‑party behavioral and conversion data so you can build analytics, insights, signals, and decisions on top of a unified event stream.

API keys and documentation

  • Keys: Publishable keys (pk_…) for browser and secret keys (sk_…) for server-side use are available in the OneLence dashboard: API keys.
  • Guides: For integration patterns, alternative integration types (e.g. server-only, tag managers), and deeper technical documentation, see the Integrations overview.

Installation

npm install @crelora/mark
# or
yarn add @crelora/mark

Runtime Imports

  • Browser runtime: import { Mark } from '@crelora/mark'
  • Node runtime: import { createNodeMark } from '@crelora/mark/node'

CDN / Script Tag Usage

You can also drop the SDK directly into your browser applications via a script tag. This exposes the global window.Mark object.

<!-- Use a CDN like unpkg or jsdelivr -->
<script src="https://unpkg.com/@crelora/mark@latest/dist/browser.umd.js"></script>

<script>
  // Mark is available globally
  Mark.init({
    key: 'pk_xxxxx',
    // ... configuration
  });

  Mark.track('Page View');
</script>

Browser Quickstart

import { Mark } from '@crelora/mark';

Mark.init({
  key: 'pk_xxxxx',
  require_consent: 'auto',
  cross_domain: { cookie_domain: '.example.com' },
  site_id: 'uuid-...',           // Optional: associate events with a registered site
  site_host: 'shop.example.com', // Optional: site host for audit/debug
  autocapture: { pageview: true }, // Optional: auto-emit page_view on load and SPA route changes
});

Mark.identify('user_123', { 
  email: '[email protected]',
  display_name: 'John Doe',
  language: 'en-US'
});
Mark.track('Checkout Started', { value: 12900, currency: 'usd' });
Mark.setConsent('granted');

// Per-event site overrides (optional)
Mark.track('Conversion', {
  site_id: 'different-site-uuid',  // Overrides init config for this event
  site_host: 'other.example.com',
  value: 5000,
});

Mark.init should be called once during app bootstrap. Subsequent calls to track, identify, or setConsent reuse the same client instance.

Node / Server Usage

import { createNodeMark } from '@crelora/mark/node';

const mark = createNodeMark({
  key: process.env.MARK_SECRET_KEY!,
  site_id: process.env.MARK_SITE_ID,     // Optional: associate events with a registered site
  site_host: process.env.MARK_SITE_HOST, // Optional: site host for audit/debug
});

mark.track('Server Conversion', {
  visitor_id: 'vis_abc',
  order_id: 'ord_789',
  value: 19900,
});

The Node factory accepts optional custom storage or transport adapters so you can plug the SDK into queues or serverless environments.

Available Methods

  • Mark.init(config) / createNodeMark(config) – bootstraps the client with your publishable or secret key.
  • track(eventName, payload?) – records custom events with arbitrary properties (numbers, strings, arrays, objects). Use site_id and site_host for per-event site overrides.
  • conversion(eventName, payload?) – records conversion events (same endpoint path as track, with is_conversion: true for backend compatibility).
  • identify(userId, traits?) – ties a known user identifier to previous anonymous activity. Recommended traits: email, display_name, language. Custom traits are also supported.
  • setConsent(status) – enforces consent gating; pass 'granted' or 'denied'.
  • getVisitorId() – returns the current pseudonymous visitor ID when available. On the browser, returns undefined until consent is granted when require_consent is set. Use it to send the ID to your backend (e.g. in a header or body) for server-side attribution when you don't have an authenticated user ID.
  • flush() – flushes queued/persisted delivery items.
  • reset() – clears user/session/attribution state and rotates visitor identity for logout flows.
  • getStats() – returns runtime delivery stats { queued, sent, failed, dropped }.
  • setInternal(value) / getInternal() – flags the current visitor as internal traffic (QA, staff, smoke tests). While set, every outgoing event is stamped with is_internal: true so the backend can exclude it from customer-facing reports by default. See Flagging internal traffic.

Reserved SDK fields (for example event_name, user_id, consent_state, and internal identity metadata) are sanitized from user payloads/traits and cannot override SDK-managed values.

Automatic Attribution Tracking

The SDK automatically captures and persists the following URL parameters:

  • UTM keys: utm_source, utm_medium, utm_campaign, utm_term, utm_content
  • Referral aliases: ref, referral, affiliate_id (normalized to ref)
  • Generic IDs: click_id, ch_click_id, cid, campaign_id
  • Paid platform IDs: gclid, gbraid, wbraid, dclid, msclkid, fbclid, ttclid, twclid, li_fat_id

These parameters are included in every tracked event to ensure proper attribution.

Capture is evaluated at SDK initialization and refreshed before each auto page view emit when autocapture.pageview + route tracking are enabled.

  • If consent is not yet granted (require_consent: true or 'auto'), attribution is kept in runtime memory and not persisted.
  • After consent is granted, pending in-memory attribution and current URL attribution are persisted.

Custom Query Param Capture

You can expand the default attribution allowlist or opt into full query capture:

Mark.init({
  key: 'pk_xxxxx',
  capture_query_params: ['sub_id1', 'sub_id2', 'publisher_code'],
  // Optional:
  // capture_all_query_params: true,
  // query_param_denylist: ['email', 'token', 'auth'],
  // max_captured_query_params: 30,
  // max_query_param_value_length: 256,
});

Configuration Reference

All config options use snake_case. Stored event payloads and database columns match 1:1.

| Option | Type | Description | | --- | --- | --- | | key | string | Publishable (browser) or secret (server) key from OneLence API keys. | | debug | boolean | Enables verbose console logging to help with integration tests. | | before_send | (event) => event \| null | Mutate/redact payloads before send, or return null to drop events. | | on_error | (error, event?) => void | Hook for transport and queue failures. | | sample_rate | number | Fraction (0..1) for track event sampling. identify and conversion are never sampled. | | honor_dnt | boolean | When true, blocks tracking when browser DNT/GPC is enabled. | | session_timeout_ms | number | Inactivity window for rotating session_id (default 30 minutes). | | request_timeout_ms | number | HTTP timeout per request in milliseconds (default 10000). | | rotate_visitor_on_consent_change | boolean | Rotate visitor_id after denied -> granted transition. | | batching | { enabled?: boolean, max_size?: number, flush_interval_ms?: number, endpoint_path?: string } | Optional batch mode (/events by default). | | require_consent | boolean \| 'auto' | true blocks tracking until consent is granted, 'auto' requires stored granted consent and treats missing consent as denied, default false ('auto' recommended for production). | | consent_source | { type: 'tcf', purposes: number[] } | Optional IAB TCF v2 integration: the SDK listens for CMP updates and only allows tracking when the listed numeric purpose IDs are consented. Combine with require_consent and setConsent as your legal team requires. | | autocapture | { pageview?: boolean, click?: boolean \| { selector?: string }, form_submit?: boolean, outbound_link?: boolean, scroll_depth?: boolean, web_vitals?: boolean } | Auto-capture toggles for page views and optional interaction/perf signals. | | track_route_changes | boolean | When autocapture.pageview is true, also emits on SPA route changes (pushState/replaceState/popstate); defaults to true. | | include_page_context | boolean | When true (default), enriches events with page, title, referrer, site (full url is only sent if you pass it explicitly in payload). | | cross_domain | CrossDomainConfig | Controls cookie domain, bridge URL, and allowlist for multi-domain attribution. | | site_id | string | Optional UUID for associating events with a registered site. Included in all event payloads as site_id. | | site_host | string | Optional site host for audit/debug purposes. Included in all event payloads as site_host. If not provided, browser SDK uses window.location.host. | | capture_query_params | string[] | Additional query keys to capture for attribution (merged into the default allowlist). | | capture_all_query_params | boolean | Capture all query params after denylist/limits. Defaults to false. | | query_param_denylist | string[] | Query keys that are never captured (exact-key matching; default includes sensitive keys like email, token, password). | | max_captured_query_params | number | Maximum number of captured query keys stored per visitor (default 30). | | max_query_param_value_length | number | Maximum stored length per captured query value (default 256). |

Server runtimes can also pass storage, storageDefaults, or transport via createNodeMark to fully control persistence and delivery.

Consent & Privacy

These behaviors matter for compliance-sensitive setups (GDPR-style consent, CMPs, enterprise security reviews):

  • Consent gating: Use require_consent: true or 'auto' so events and identify only run after a positive consent signal. In 'auto' mode, missing consent is treated as denied until setConsent('granted').
  • TCF v2: Set consent_source: { type: 'tcf', purposes: [/* IAB purpose IDs */] } so tracking follows your CMP’s current purpose consents (the SDK subscribes to CMP updates rather than relying on a one-time read).
  • Withdrawal: setConsent('denied') stops tracking and clears stored attribution plus cookie-backed visitor identity where applicable.
  • Stricter identity hygiene: Enable rotate_visitor_on_consent_change if you want a fresh visitor_id after a denied → granted transition.
  • DNT / GPC: honor_dnt: true blocks tracking when the browser reports Do Not Track or Global Privacy Control.
  • Data minimization: Use before_send to strip or redact fields before they leave the client; use on_error for observability without logging raw payloads.
  • Payloads cannot bypass consent via event properties; reserved fields are sanitized.
  • Pre-consent attribution: URL attribution is held in memory only until consent is granted, then persisted.
  • Cross-domain: First-party iframe bridges keep identifiers under your control.
  • Delivery without long-lived local queues: Failed sends can be retried from a browser outbox with a 48-hour TTL; on tab hide / unload, pending items are flushed with sendBeacon where possible to improve delivery without weakening consent checks.
  • IP / geo: IP is not read in the browser; it is taken server-side, hashed, and used for coarse geo only when allowed by consent and tenant settings.

For product-level privacy commitments and processor terms, rely on your OneLence agreement and documentation; this README describes SDK behavior only.

Visitor ID for server attribution

When you don't have an authenticated user ID, you can pass the SDK's visitor ID to your backend so server-side events (e.g. checkout, webhooks) are attributed to the same visitor as browser events.

Browser: Call Mark.getVisitorId() after init. If you use require_consent: true or 'auto', it returns undefined until consent is granted. Once available, send it in a header or request body to your API and use it as the visitor_id when calling the Node SDK or your ingestion.

Node: Call mark.getVisitorId() to read the visitor ID from the storage you passed to createNodeMark (e.g. from storageDefaults.visitor_id). Use it to associate server events with the same visitor.

The visitor ID is a pseudonymous, SDK-scoped identifier. Do not use it as a cross-site or long-term user identity; use it only for joining browser and server events within your attribution flow.

User Identification

The identify() method links anonymous visitors to known users. Recommended traits:

  • email (string) - User's email address. Will be hashed server-side for privacy.
  • display_name (string) - User's display name or full name.
  • language (string) - User's preferred language code (e.g., 'en', 'en-US', 'fr').
  • phone (string) - User's phone number. Will be hashed server-side for privacy.

You can also include any custom traits as key-value pairs. All traits are stored in the user profile and can be used for segmentation and personalization.

Mark.identify('user_123', {
  email: '[email protected]',
  display_name: 'John Doe',
  language: 'en-US',
  plan: 'premium',
  signup_date: '2024-01-15',
});

Page Views

  • Event name: page_view (canonical). Use display names in your product UI if you prefer human-readable labels.
  • Autocapture (optional): set autocapture: { pageview: true } (and optionally track_route_changes: true) to emit on first load and SPA route changes. If consent is required and not yet granted, initial pageview is deferred and emitted once consent is granted.
  • All SDK config and event fields use snake_case (site_id, site_host, etc.) and map directly to stored payloads and database columns.

Extended autocapture (browser)

All of the following are opt-in under autocapture. They respect the same consent, DNT/GPC, and sampling rules as track() (see sample_rate: conversions are exempt; these modes are not conversion events). They are registered when Mark.init() runs.

| Flag | Event name(s) | Behavior | | --- | --- | --- | | click | data-mark-event value, or click | Listens for clicks. Default: only elements matching [data-mark-event]; set click: { selector: '.my-tracked' } to use a custom selector. Sends element_id, element_classes, truncated text, and href when applicable. | | form_submit | form_submit | Listens for submit; sends form_id, form_name, action. | | outbound_link | outbound_link_click | On click, if the link target is another origin, sends href. Uses preferBeacon: true so the event is more likely to fire before navigation. | | scroll_depth | scroll_depth | Fires at 25%, 50%, 75%, and 100% of vertical scroll depth (once each per page load). Payload includes percent. | | web_vitals | web_vital | Lazy-loads the web-vitals dependency and reports LCP, CLS, INP, and TTFB with metric, value, optional rating, and metric_id. If the module fails to load, only debug: true logs a warning. |

Example:

Mark.init({
  key: 'pk_xxxxx',
  require_consent: 'auto',
  autocapture: {
    pageview: true,
    click: true,
    // or: click: { selector: '[data-analytics]' },
    form_submit: true,
    outbound_link: true,
    scroll_depth: true,
    web_vitals: true,
  },
});

Flagging internal traffic

Use the is_internal marker to keep QA sessions, staff testing, or automated smoke tests out of customer-facing reports without dropping them on the client. The SDK supports it in two complementary ways:

1. Per-event field. Any track / conversion call can carry is_internal: true:

Mark.track('checkout_started', { value: 1299, is_internal: true });

2. Persistent visitor flag. Set it once and every subsequent event is stamped automatically:

Mark.setInternal(true);  // stamps is_internal: true on all future events
Mark.setInternal(false); // stops stamping
Mark.getInternal();      // boolean

The persistent flag is stored in the SDK's browser storage so it survives reloads. It is cleared automatically by Mark.reset() and by Mark.setConsent('denied').

Design note. The SDK intentionally does not read a magic URL parameter or write to a well-known localStorage key on its own — that would let anyone opt themselves out of your analytics just by visiting a URL, and it would bake a specific policy into every integration. Instead, your app decides when to flip the flag. A common pattern is a URL query parameter for staff onboarding:

// After Mark.init(...)
const params = new URLSearchParams(window.location.search);
if (params.get('onelence_internal') === '1') Mark.setInternal(true);
if (params.get('onelence_internal') === '0') Mark.setInternal(false);

Other reasonable triggers: an authenticated admin/staff role, an internal IP range detected server-side, a feature flag, or an explicit toggle in your own settings UI.

Server-side the same API is available on NodeMark:

const mark = createNodeMark({ key: process.env.MARK_SECRET_KEY! });
mark.setInternal(true);
mark.track('backoffice_action'); // is_internal: true

is_internal is a first-class field on the backend: events are still ingested (so you can audit and debug integrations), but excluded from customer-facing reports by default.

Support

Need help? Reach out through your account team or file a ticket via the OneLence dashboard. Please include the SDK version, runtime (browser or Node), and any reproduction steps so we can assist quickly.