@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-sdk2. 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 trackproperties(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 nameproperties(Record<string, any>, optional): Event propertiestime(number, required): Timestamp in millisecondsanon_id(string, optional): Anonymous user IDlogin_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 preferentiallytrace_id(string, required): Trace ID for request trackinguser_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 (anErrorinstance 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 watchoption(Object, required):eventName(string, required): The exposure event nameconfig(Object, optional): Per-element exposure config —visibleRatio(0–1),stayDuration(seconds),repeated(boolean). Keys not specified fall back to the existing registration / globalexposureConfig/ built-in defaultsproperties(Object, optional): Custom properties attached to the exposure event. May override the$element_*properties of the same namelistener(Object, optional):shouldExpose(ele, props)— returnfalseto 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
anonIdconfig 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, defaultfalse): 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:
- Automatic capture — enable it with
enableErrorTrack: trueininit(). The SDK installs global listeners (capture-phaseerror+unhandledrejection) and captures:- Uncaught JS errors (with or without an
Errorobject, including cross-origin"Script error.") - Unhandled promise rejections
- Resource load failures (
<script>,<img>,<link>, etc.)
- Uncaught JS errors (with or without an
- Manual reporting — call
trackException(error, properties?)anywhere you already catch an error (see trackException). This is NOT gated byenableErrorTrack/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_framesarray (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
Errorobject, e.g. cross-origin"Script error."built fromfilename: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 onvisible) - In SPAs (
isSinglePageApp: true), declarative registrations are re-scanned on each route change, while elements registered viaaddExposureView()are preserved
Registration
Three ways to register elements (they can be combined):
- Global defaults — pass
exposureConfigininit():
SensorsWave.init('your-source-token', {
apiHost: 'https://your-api-host',
enableExposureTrack: true,
exposureConfig: { visibleRatio: 0, stayDuration: 2, repeated: true }
});- 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, 0–1 |
| 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 |
- JS API —
addExposureView()/removeExposureView()(see addExposureView).
Config precedence (highest → lowest): element individual attributes → element data-sw-exposure-option → addExposureView 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:
- The element enters the viewport with
intersectionRatio >= visibleRatio→ a dwell timer ofstayDurationseconds starts. Leaving the viewport cancels the timer, sostayDurationmeans continuous visibility. Timers never start while the page is in a hidden tab (background tabs are ignored until they become visible) - When the timer fires, the SDK re-validates the element (non-zero size, still attached to the document, not opted out) and consults
shouldExpose - The event is sent with the
$element_*properties plus custom properties, thendidExposeruns repeated: truere-arms immediately: the element re-exposes when it leaves and re-enters the viewport (not on a timer).repeated: falseexposes once per registration
Note:
visibleRatiomust be achievable — an element taller than the viewport can never be more thanviewport height / element heightvisible, so anvisibleRatioclose to1will 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
enableClickTrackis 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 whenenableErrorTrackis true), or reported manually viatrackException() - Exposure: Triggered when a registered element has been continuously visible for
stayDurationseconds atvisibleRatioratio (only whenenableExposureTrackis true). The event name is supplied by the caller viadata-sw-exposure-event-nameoraddExposureView()— it is not a preset event
Custom events can be tracked using the trackEvent() or track() methods.
License
Apache-2.0
