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

@sensorswave/js-sdk

v1.5.0

Published

Sensors Wave JS SDK for web analytics

Readme

Sensors Wave JS SDK

Sensors Wave JS SDK is a web analytics tracking library. If you're new to Sensor Wave, check out our product and create an account at sensorswave.com.

SDK Usage

1. Install via npm

npm install @sensorswave/js-sdk

2. Import as ES Module

import SensorsWave from '@sensorswave/js-sdk';
SensorsWave.init('your-source-token', {
  debug: false,
  apiHost: 'https://your-api-host.com',
  autoCapture: true
});

3. Import via Script Tag

<script src="/path/to/index.js"></script>
<script>
  SensorsWave.init('your-source-token', {
    debug: false,
    apiHost: 'https://your-api-host.com',
    autoCapture: true
  });
</script>

4. Send Custom Events

SensorsWave.trackEvent('ButtonClick', {
  button_name: 'submit',
  page: 'home'
});

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | debug | boolean | false | Whether to enable debug mode for logging | | apiHost | string | '' | API host address for sending events | | autoCapture | boolean | true | Whether to automatically capture page events (page view, page load, page leave) | | enableClickTrack | boolean | false | Whether to enable automatic click tracking on all elements | | isSinglePageApp | boolean | false | Whether the application is a Single Page Application (enables automatic route change tracking) | | crossSubdomainCookie | boolean | true | Whether to share cookies across subdomains | | enableAB | boolean | false | Whether to enable A/B testing feature | | abRefreshInterval | number | 600000 (10 minutes) | The interval in milliseconds for refreshing A/B test configuration | | enableErrorTrack | boolean | false | Whether to automatically capture error-level exceptions ($Exception): uncaught JS errors, unhandled promise rejections and resource load failures. Independent of autoCapture. Effective in web environment | | enableCrashTrack | boolean | false | Whether to enable app crash tracking (fatal level). Only effective in app host environments (iOS/Android/HarmonyOS WebView); NOT effective in this pure web SDK (the option is reserved to keep one unified config spec across SDKs) | | enableExposureTrack | boolean | false | Whether to enable element exposure tracking. Elements are registered declaratively via data-sw-exposure-* attributes or programmatically via addExposureView(). Requires IntersectionObserver + MutationObserver (unsupported browsers silently skip initialization). Independent of autoCapture | | exposureConfig | object | { visibleRatio: 0, stayDuration: 0, repeated: true } | Global exposure defaults: visibleRatio (visible-area ratio, 0–1), stayDuration (seconds of continuous visibility), repeated (re-expose on re-entry). Lowest precedence — can be overridden per element | | batchSend | boolean | false | Whether to use batch sending (sends events in batches up to 10 events every 5 seconds) | | anonId | string | '' | User-provided anonymous ID. When set, it overrides the SDK-generated anonymous ID and persists locally (cookie), so it is reused on subsequent visits even if not passed again |

API Methods

Event Tracking

trackEvent

Manually track a custom event with properties.

Parameters:

  • eventName (string, required): The name of the event to track
  • properties (Object, optional): Additional properties to include with the event

Example:

SensorsWave.trackEvent('ButtonClick', {
  button_name: 'submit',
  page: 'home',
  category: 'user_action'
});

track

Track an event with full control over the event structure. This is an advanced method that allows you to specify all event fields manually.

Parameters:

  • event (AdvanceEvent, required): Complete event object with the following structure:
    • event (string, required): Event name
    • properties (Record<string, any>, optional): Event properties
    • time (number, required): Timestamp in milliseconds
    • anon_id (string, optional): Anonymous user ID
    • login_id (string, optional): Logged-in user ID。At least one of the login_id and anon_id should be passed. When passed simultaneously, the login_id should be used preferentially
    • trace_id (string, required): Trace ID for request tracking
    • user_properties (Record<string, any>, optional): User properties

Example:

SensorsWave.track({
  event: 'PurchaseCompleted',
  properties: {
    product_id: '12345',
    amount: 99.99,
    currency: 'USD'
  },
  time: Date.now(),
  trace_id: 'unique-trace-id-12345',
  anon_id: 'anonymous-user-id',
  login_id: 'user_12345',
  user_properties: {
    plan: 'premium',
    signup_date: '2024-01-01'
  }
});

trackException

Manually report a caught exception as an $Exception event (level is fixed to error).

This method is NOT gated by enableErrorTrack / enableCrashTrack; it only respects the consent guard (SDK initialized and not opted out). The stack is normalized and truncated the same way as automatically captured exceptions.

Parameters:

  • error (Error | string, required): The caught error (an Error instance or a string)
  • properties (Object, optional): Additional properties to attach. Cannot override the reserved $exception_* properties

Example:

try {
  doSomethingRisky();
} catch (err) {
  SensorsWave.trackException(err, { order_id: '123' });
}

addExposureView

Register an element for exposure tracking. The event name is supplied by the caller (required) — there is no preset exposure event name.

Only effective when enableExposureTrack: true was set in init() (and the consent guard passes: SDK initialized and not opted out). See Exposure Tracking for the full trigger algorithm and declarative alternative.

Parameters:

  • ele (HTMLElement, required): The element to watch
  • option (Object, required):
    • eventName (string, required): The exposure event name
    • config (Object, optional): Per-element exposure config — visibleRatio (0–1), stayDuration (seconds), repeated (boolean). Keys not specified fall back to the existing registration / global exposureConfig / built-in defaults
    • properties (Object, optional): Custom properties attached to the exposure event. May override the $element_* properties of the same name
    • listener (Object, optional): shouldExpose(ele, props) — return false to skip this exposure; didExpose(ele, props) — called after the event is sent

Example:

SensorsWave.addExposureView(document.getElementById('banner'), {
  eventName: 'home_top_banner',
  config: { visibleRatio: 0.5, stayDuration: 2, repeated: true },
  properties: { position: 'top' },
  listener: {
    shouldExpose: function (ele, props) { return true; },
    didExpose: function (ele, props) { console.log('exposed', props); }
  }
});

removeExposureView

Remove an element's exposure listener and cancel any pending dwell timer.

Parameters:

  • ele (HTMLElement, required): The element to unregister

Example:

SensorsWave.removeExposureView(document.getElementById('banner'));

User Profile

profileSet

Set user properties. If a property already exists, it will be overwritten.

Parameters:

  • properties (Object, required): User properties to set

Example:

SensorsWave.profileSet({
  name: 'John Doe',
  age: 30,
  plan: 'premium'
});

profileSetOnce

Set user properties only if they don't already exist. Existing properties will not be overwritten.

Parameters:

  • properties (Object, required): User properties to set once

Example:

SensorsWave.profileSetOnce({
  signup_date: '2024-01-15',
  initial_referrer: 'google',
  initial_campaign: 'spring_sale'
});

profileIncrement

Increment numeric user properties by a specified amount. Only supports numeric properties.

Parameters:

  • properties (Object, required): Properties to increment with numeric values

Example:

// Increment single property
SensorsWave.profileIncrement({
  login_count: 1
});

// Increment multiple properties
SensorsWave.profileIncrement({
  login_count: 1,
  points_earned: 100,
  purchases_count: 1
});

profileAppend

Append new values to list-type user properties without deduplication.

Parameters:

  • properties (Object, required): Properties with array values to append

Example:

SensorsWave.profileAppend({
  categories_viewed: ['electronics', 'mobile_phones'],
  tags: ['new_customer', 'q1_2024']
});

profileUnion

Append new values to list-type user properties with deduplication (avoids duplicate values).

Parameters:

  • properties (Object, required): Properties with array values to append with deduplication

Example:

SensorsWave.profileUnion({
  interests: ['technology', 'gaming'],
  newsletter_subscriptions: ['tech_news']
});

profileUnset

Set specific user properties to null (effectively removing them).

Parameters:

  • propertyNames (string | string[], required): Property name(s) to unset

Example:

// Unset single property
SensorsWave.profileUnset('temporary_campaign');

// Unset multiple properties
SensorsWave.profileUnset(['old_plan', 'expired_flag', 'temp_id']);

profileDelete

Delete all user profile data for the current user. This operation cannot be undone.

Example:

SensorsWave.profileDelete();

User Identification

identify

Set the login ID for the current user and send a binding event to associate anonymous behavior with the identified user.

Parameters:

  • loginId (string | number, required): Unique identifier for the user (e.g., email, user ID, username)

Example:

SensorsWave.identify('user_12345');

setLoginId

Set the login ID for the current user without sending a binding event. Use this if you want to identify the user but don't need to track the association event.

Parameters:

  • loginId (string | number, required): Unique identifier for the user

Example:

SensorsWave.setLoginId('user_12345');

setAnonId

Set a user-provided anonymous ID for the current user. It takes the highest priority — overriding the SDK-generated anonymous ID — and persists locally (cookie), so it is reused on subsequent visits even if not set again. Useful for cross-device or server-side identity linking.

Parameters:

  • anonId (string | number, required): The anonymous ID to set

Example:

SensorsWave.setAnonId('custom-anonymous-id');

You can also pass it once at initialization via the anonId config option: SensorsWave.init('token', { anonId: 'custom-anonymous-id' }).

getLoginId

Get the current login ID for the identified user.

Returns: string | number

Example:

const loginId = SensorsWave.getLoginId();
console.log('Current login ID:', loginId);

getAnonId

Get the current anonymous ID for the user. By default this is automatically generated by the SDK; if an anonymous ID was provided via the anonId config or setAnonId(), that value is returned instead. It persists across sessions.

Returns: string

Example:

const anonId = SensorsWave.getAnonId();
console.log('Anonymous user ID:', anonId);

reset

Call when the user logs out to unbind the login ID from the current device. After calling reset(), subsequent events are no longer associated with the logged-in user.

By default the anonymous ID is preserved, so the device continues to be tracked as an anonymous user. Pass true to also reset the anonymous ID and generate a new one — useful for shared/public devices where the previous visitor's anonymous identity should not be reused.

Parameters:

  • resetAnonymousId (boolean, optional, default false): Whether to also reset the anonymous ID

Example:

// 用户登出时
SensorsWave.reset();  // 默认保留匿名 ID

// 如果需要同时重置匿名 ID(如公共设备场景)
SensorsWave.reset(true);

Common Properties

registerCommonProperties

Register static or dynamic common properties that will be included with all events. This is useful for including global context like app version, environment, or user-specific data.

Parameters:

  • properties (Record<string, string | Function>, required): Properties to register
    • Static properties: string values
    • Dynamic properties: functions that return values (evaluated for each event)

Example:

SensorsWave.registerCommonProperties({
  // Static property
  app_version: '1.0.0',
  environment: 'production',

  // Dynamic property (evaluated for each event)
  current_time: () => new Date().toISOString(),
  user_session_id: () => getSessionId(),

  // You can also include user-specific data
  user_tier: () => getUserTier()
});

clearCommonProperties

Remove specific registered common properties.

Parameters:

  • propertyNames (string[], required): Array of property names to remove

Example:

SensorsWave.clearCommonProperties(['app_version', 'user_session_id']);

A/B Testing

checkFeatureGate

Check if a feature gate (feature flag) is enabled for the current user. Returns a promise that resolves to a boolean.

Parameters:

  • key (string, required): The feature gate key to check

Returns: Promise

Example:

// Check if a feature is enabled
SensorsWave.checkFeatureGate('new_checkout_flow')
  .then(isEnabled => {
    if (isEnabled) {
      // Show new feature
      showNewCheckout();
    } else {
      // Show old feature
      showOldCheckout();
    }
  });

// Using async/await
async function initFeature() {
  const isEnabled = await SensorsWave.checkFeatureGate('advanced_search');
  if (isEnabled) {
    enableAdvancedSearch();
  }
}

getExperiment

Get experiment variant data for the current user. Returns a promise that resolves to the experiment configuration.

Parameters:

  • key (string, required): The experiment key to retrieve

Returns: Promise

Returns an empty object {} by default when the experiment key is not found or the A/B testing feature is not enabled.

The returned object contains:

  • Record<string, any>: Variant configuration values

Example:

// Get experiment configuration
SensorsWave.getExperiment('homepage_layout')
  .then(experiment => {
    if (Object.keys(experiment).length > 0) {
      // Apply experiment configuration
      applyLayout(experiment.layout_type);
    }
  });

// Using async/await
async function initExperiment() {
  const experiment = await SensorsWave.getExperiment('pricing_display');
  if (experiment) {
    const { price_format, discount_type } = experiment;
    updatePricingDisplay(price_format, discount_type);
  }
}

getFeatureConfig

Get feature configuration data for the current user. Returns a promise that resolves to the feature configuration object. The server returns a JSON string that is automatically parsed.

Parameters:

  • key (string, required): The feature config key to retrieve

Returns: Promise

Returns an empty object {} by default when the feature config key is not found or the A/B testing feature is not enabled.

The returned object contains:

  • Record<string, any>: Feature configuration values (parsed from JSON string)
  • If JSON parsing fails, the raw string is returned

Example:

// Get feature configuration
SensorsWave.getFeatureConfig('app_settings')
  .then(config => {
    if (Object.keys(config).length > 0) {
      // Apply feature configuration
      applySettings(config);
    }
  });

// Using async/await
async function initFeatureConfig() {
  const config = await SensorsWave.getFeatureConfig('ui_config');
  if (config) {
    const { theme, layout, features } = config;
    updateUI(theme, layout, features);
  }
}

Error Tracking

The SDK reports exceptions as $Exception events. Exceptions come from two paths:

  1. Automatic capture — enable it with enableErrorTrack: true in init(). The SDK installs global listeners (capture-phase error + unhandledrejection) and captures:
    • Uncaught JS errors (with or without an Error object, including cross-origin "Script error.")
    • Unhandled promise rejections
    • Resource load failures (<script>, <img>, <link>, etc.)
  2. Manual reporting — call trackException(error, properties?) anywhere you already catch an error (see trackException). This is NOT gated by enableErrorTrack / enableCrashTrack.

$Exception Event Properties

Every $Exception event carries the following reserved $exception_* properties:

| Property | Type | Description | |----------|------|-------------| | $exception_level | string | Severity level. Always error in this SDK (the fatal crash level only exists in app-host SDKs) | | $exception_type | string | Exception type. For an Error object: its name (e.g. TypeError, RangeError), falling back to the type inferred from the stack header, then Error. Special values: UnhandledRejection (non-Error promise rejection reason) and ResourceLoadError (resource load failure). Capped at 200 chars | | $exception_message | string | Exception message: error.message for Error objects, the raw message for string-form errors, a stringified reason for rejections (JSON for objects), or Failed to load <tag> from <url> for resource failures. Capped at 1000 chars | | $exception_frames | ExceptionFrame[] | Structured stack frames derived from parsing error.stack (see below). This is the input for server-side symbolication (sourcemap) and aggregation. Empty array [] when there is no stack source (resource load failures, non-Error rejections, string-form reports) |

Custom properties passed to trackException() are attached alongside these, but cannot override the reserved $exception_* properties.

Stack Parsing

Stacks are parsed (V8/Gecko formats) before reporting:

  • At most 30 frames are kept
  • The page origin prefix, query string and hash are stripped from each frame's path (same-origin scripts become relative paths)
  • Consecutive repeated frames are kept individually rather than collapsed
  • Parsed frames are reported as the structured $exception_frames array (see below)

Structured Frames ($exception_frames)

Each event carries the parsed stack as a structured frame array for server-side symbolication and aggregation. Field semantics follow PostHog's StackFrame:

| Field | Type | Description | |-------|------|-------------| | platform | string | Always web:javascript in this SDK | | filename | string | Cleaned path (page-origin prefix, query string and hash stripped) — the symbolication and aggregation key | | function | string | Original function name (minified in compressed builds); ? for anonymous frames | | lineno / colno | number | Line and column as numbers. Note V8 columns are 1-based — subtract 1 when indexing a sourcemap | | abs_path | string | The original URL before cleaning (query/hash version hints preserved), capped at 1000 chars — filename cleaning is irreversible, this recovers the loss | | module | string | Fully-qualified class name, only written by Java hosts — never set by this SDK |

Notes:

  • No collapsing or char-length truncation — the 30-frame parse limit bounds the payload, each frame is kept individually
  • Synthetic frames (no Error object, e.g. cross-origin "Script error." built from filename:lineno:colno) are also emitted as a single-element array

Exposure Tracking

The SDK can report an event when an element has been continuously visible in the viewport for a configured ratio and duration. Enable it with enableExposureTrack: true in init().

  • The event name is supplied by the caller (required) — exposure events are custom events, not preset $-events
  • Requires IntersectionObserver + MutationObserver; in unsupported browsers the feature silently skips initialization
  • Dwell time does not accumulate while the tab is in the background (visibility is paused on hidden, resumed on visible)
  • In SPAs (isSinglePageApp: true), declarative registrations are re-scanned on each route change, while elements registered via addExposureView() are preserved

Registration

Three ways to register elements (they can be combined):

  1. Global defaults — pass exposureConfig in init():
SensorsWave.init('your-source-token', {
  apiHost: 'https://your-api-host',
  enableExposureTrack: true,
  exposureConfig: { visibleRatio: 0, stayDuration: 2, repeated: true }
});
  1. Declarative attributes — mark elements with data-sw-exposure-* attributes; they are auto-discovered on page load, on SPA route changes and whenever the DOM changes:
<div
  data-sw-exposure-event-name="home_top_banner"
  data-sw-exposure-config-visible-ratio="0.5"
  data-sw-exposure-config-stay-duration="2"
  data-sw-exposure-config-repeated="true"
  data-sw-exposure-property-position="top"
></div>

<!-- Or set everything in one JSON attribute (lower precedence than the individual attributes); config keys are camelCase -->
<div
  data-sw-exposure-event-name="promo_card"
  data-sw-exposure-option='{"config":{"visibleRatio":0.8},"properties":{"slot":"card"}}'
></div>

| Attribute | Required | Description | |-----------|----------|-------------| | data-sw-exposure-event-name | yes | The exposure event name (must be non-empty) | | data-sw-exposure-config-visible-ratio | no | Visible-area ratio, 01 | | data-sw-exposure-config-stay-duration | no | Required continuous-visibility duration in seconds | | data-sw-exposure-config-repeated | no | "true" / "false" — whether re-entry re-triggers | | data-sw-exposure-property-* | no | Custom property for this element's exposure event. Values are strings. Note: HTML lowercases attribute names, so data-sw-exposure-property-BannerType yields the property key bannertype — use lowercase / kebab-case keys | | data-sw-exposure-option | no | JSON { "config": {...}, "properties": {...} } — a single-attribute alternative with lower precedence than the individual attributes |

  1. JS APIaddExposureView() / removeExposureView() (see addExposureView).

Config precedence (highest → lowest): element individual attributes → element data-sw-exposure-optionaddExposureView config → global exposureConfig → built-in defaults ({ visibleRatio: 0, stayDuration: 0, repeated: true }). Config keys are camelCase in both JS and the option JSON; the individual data-sw-exposure-config-* attributes use kebab-case (HTML lowercases attribute names, so camelCase attribute names cannot be read back).

Trigger Algorithm

For each registered element, an IntersectionObserver (viewport root, threshold = visibleRatio, one observer instance per distinct visibleRatio) drives the following:

  1. The element enters the viewport with intersectionRatio >= visibleRatio → a dwell timer of stayDuration seconds starts. Leaving the viewport cancels the timer, so stayDuration means continuous visibility. Timers never start while the page is in a hidden tab (background tabs are ignored until they become visible)
  2. When the timer fires, the SDK re-validates the element (non-zero size, still attached to the document, not opted out) and consults shouldExpose
  3. The event is sent with the $element_* properties plus custom properties, then didExpose runs
  4. repeated: true re-arms immediately: the element re-exposes when it leaves and re-enters the viewport (not on a timer). repeated: false exposes once per registration

Note: visibleRatio must be achievable — an element taller than the viewport can never be more than viewport height / element height visible, so an visibleRatio close to 1 will never trigger for tall elements.

Exposure Event Properties

| Property | Description | |----------|-------------| | $element_type | Tag name, lowercased | | $element_name | name attribute | | $element_id | id attribute | | $element_class_name | class attribute | | $element_target_url | href attribute | | $element_content | Text content (255-char cap; input uses the value for button/submit types) | | $element_selector | CSS-like selector chain | | $element_path | DOM path chain |

Custom properties are merged on top and may override the $element_* properties of the same name. Note: exposure has no click context, so there are no $page_x / $page_y properties.

Supported Event Types

The SDK automatically captures the following event types when autoCapture is enabled:

  • PageView: Triggered when a user views a page
  • PageLoad: Triggered when a page finishes loading
  • PageLeave: Triggered when a user is about to leave a page
  • WebClick: Triggered on element clicks (only when enableClickTrack is true)

Additional automatic events:

  • Exception ($Exception): Triggered when an error-level exception is captured — uncaught JS errors, unhandled promise rejections, and resource load failures (only when enableErrorTrack is true), or reported manually via trackException()
  • Exposure: Triggered when a registered element has been continuously visible for stayDuration seconds at visibleRatio ratio (only when enableExposureTrack is true). The event name is supplied by the caller via data-sw-exposure-event-name or addExposureView() — it is not a preset event

Custom events can be tracked using the trackEvent() or track() methods.

License

Apache-2.0