Automatic page and environment context collection for SDK Kit.
- Privacy-first query param filtering - UTM params by default, opt-in for others
- External referrer tracking - Only captures external referrers with session attribution
- Cookie-based session detection - Reliable session tracking across tabs
- Storage availability detection - Know when localStorage/sessionStorage/cookies don't work
- Simple device detection - Mobile vs desktop (95% accuracy)
- Lazy collection with caching - Only collects when needed, caches result
- SPA support - Refresh context when route changes
npm install @prosdevlab/sdk-kit @prosdevlab/sdk-kit-pluginsimport { SDK } from '@prosdevlab/sdk-kit';
import { contextPlugin } from '@prosdevlab/sdk-kit-plugins';
const sdk = new SDK();
sdk.use(contextPlugin);
await sdk.init();
// Get full context
const context = sdk.context.get();
console.log(context);
// {
// page: { url, path, query, referrer, ... },
// device: { type, userAgent },
// screen: { width, height },
// environment: { timezone, language, ... },
// timestamp: 1234567890
// }// Get only what you need
const page = sdk.context.getPage();
const device = sdk.context.getDevice();
const screen = sdk.context.getScreen();
const env = sdk.context.getEnvironment();By default, only UTM parameters are captured (privacy-first):
// URL: https://example.com?utm_source=google&id=123&secret=abc
const page = sdk.context.getPage();
console.log(page.query);
// { utm_source: 'google' }
// ✅ UTM params captured
// ❌ 'id' and 'secret' NOT captured (privacy)Allow-list specific params:
const sdk = new SDK({
context: {
queryParamsAllowList: ['fbclid', 'gclid', 'campaign_id']
}
});
sdk.use(contextPlugin);
await sdk.init();
// Now captures UTM + allow-listed params
const page = sdk.context.getPage();
console.log(page.query);
// { utm_source: 'google', fbclid: '...' }Capture all params (opt-in):
const sdk = new SDK({
context: {
captureAllQueryParams: true
}
});
sdk.use(contextPlugin);
await sdk.init();
const page = sdk.context.getPage();
console.log(page.query);
// { utm_source: 'google', id: '123', secret: 'abc' }
// ⚠️ All params captured - use with cautionOnly external referrers are captured (internal navigation is ignored):
// User navigates: google.com → yoursite.com/page1 → yoursite.com/page2
// Page 1:
const page1 = sdk.context.getPage();
console.log(page1.referrer); // 'google.com' (external)
console.log(page1.sessionReferrer); // 'google.com' (first referrer)
console.log(page1.isSessionStart); // true
// Page 2:
sdk.context.refresh();
const page2 = sdk.context.getPage();
console.log(page2.referrer); // '' (internal, ignored)
console.log(page2.sessionReferrer); // undefined (not a new session)
console.log(page2.isSessionStart); // falseSessions are tracked via cookies (requires Storage Plugin):
import { storagePlugin, contextPlugin } from '@prosdevlab/sdk-kit-plugins';
const sdk = new SDK();
sdk.use(storagePlugin); // Required for session detection
sdk.use(contextPlugin);
await sdk.init();
const page = sdk.context.getPage();
console.log(page.isSessionStart); // true on first visit
console.log(page.sessionReferrer); // First external referrerCustom session config:
const sdk = new SDK({
context: {
sessionCookieName: '_my_session',
sessionDuration: 3600 // 1 hour (default: 1800 = 30 min)
}
});Refresh context when the route changes:
// React Router example
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function App() {
const location = useLocation();
useEffect(() => {
// Refresh context on route change
sdk.context.refresh();
// Track pageview with updated context
const page = sdk.context.getPage();
sdk.track('pageview', { path: page.path });
}, [location]);
return <Routes>...</Routes>;
}Simple mobile vs desktop detection (95% accuracy):
const device = sdk.context.getDevice();
if (device.type === 'mobile') {
console.log('Mobile user');
} else if (device.type === 'desktop') {
console.log('Desktop user');
}
// Raw user agent for server-side parsing
console.log(device.userAgent);Know when storage APIs don't work (privacy mode, quota exceeded, etc.):
const env = sdk.context.getEnvironment();
if (!env.hasLocalStorage) {
console.warn('localStorage unavailable - using memory fallback');
}
if (!env.hasSessionStorage) {
console.warn('sessionStorage unavailable');
}
if (!env.cookieEnabled) {
console.warn('Cookies disabled');
}Listen for context collection events:
sdk.on('context:collected', (context) => {
console.log('Context collected:', context);
});
sdk.on('context:refreshed', (context) => {
console.log('Context refreshed:', context);
});Get full context (lazy, cached).
Returns:
{
page: PageContext;
device: DeviceContext;
screen: ScreenContext;
environment: EnvironmentContext;
timestamp: number;
}Get page context only.
Returns:
{
url: string; // Full URL
path: string; // Pathname
query: Record<string, string>; // Filtered query params
utmParams: Record<string, string>; // UTM params (always captured)
hash: string; // URL hash
referrer: string; // External referrer (stripped protocol)
sessionReferrer?: string; // First external referrer of session
isSessionStart: boolean; // Is this a new session?
title: string; // Document title
}Get device context only.
Returns:
{
type: 'mobile' | 'desktop' | 'unknown';
userAgent: string; // Raw UA string
}Get screen context only.
Returns:
{
width: number; // Screen width
height: number; // Screen height
}Get environment context only.
Returns:
{
timezoneOffset: number; // Offset from UTC in hours (e.g., -8 for PST)
language: string; // Primary language (e.g., 'en-US')
cookieEnabled: boolean; // Are cookies enabled?
doNotTrack: boolean; // DNT preference
hasLocalStorage: boolean; // Is localStorage available?
hasSessionStorage: boolean; // Is sessionStorage available?
}Clear cache and re-collect context. Use this in SPAs when the route changes.
export interface ContextPluginConfig {
context?: {
// Disable the plugin
disabled?: boolean;
// Query param filtering
captureAllQueryParams?: boolean; // Default: false
queryParamsAllowList?: string[]; // Default: []
// Session detection
sessionCookieName?: string; // Default: '_sdk_session'
sessionDuration?: number; // Default: 1800 (30 minutes)
};
}| Event | Payload | Description |
|---|---|---|
context:collected |
Context |
Emitted when context is first collected |
context:refreshed |
Context |
Emitted when context is refreshed |
The Context Plugin is designed with privacy in mind:
✅ UTM params only by default - Doesn't capture sensitive query params
✅ External referrers only - Doesn't track internal navigation
✅ Opt-in for additional data - User must explicitly enable
✅ DNT support - Respects Do Not Track preference
✅ No PII - Doesn't collect personally identifiable information
- Modern browsers (Chrome, Firefox, Safari, Edge)
- Mobile browsers (iOS Safari, Chrome Mobile)
- Graceful degradation in non-browser environments (Node.js, SSR)
- Optional: Storage Plugin (for session detection)
- Without Storage Plugin, every page load is treated as a new session
This plugin follows battle-tested patterns for context collection:
- ✅ Privacy-first query param filtering
- ✅ External referrer tracking
- ✅ Cookie-based session detection
- ✅ Simple timezone offset
- ✅ Storage availability detection
- ✅ Graceful degradation
import { SDK } from '@prosdevlab/sdk-kit';
import { contextPlugin } from '@prosdevlab/sdk-kit-plugins';
const sdk = new SDK({
context: {
queryParamsAllowList: ['fbclid', 'gclid']
}
});
sdk.use(contextPlugin);
await sdk.init();
// Track pageview with context
function trackPageview() {
const context = sdk.context.get();
sdk.track('pageview', {
page: context.page.path,
referrer: context.page.referrer,
device: context.device.type,
screen: `${context.screen.width}x${context.screen.height}`,
language: context.environment.language,
timezone: context.environment.timezoneOffset
});
}
trackPageview();// Capture UTM params and ad click IDs
const sdk = new SDK({
context: {
queryParamsAllowList: ['fbclid', 'gclid', 'msclkid']
}
});
sdk.use(contextPlugin);
await sdk.init();
const page = sdk.context.getPage();
// Attribution data
const attribution = {
source: page.utmParams.utm_source,
medium: page.utmParams.utm_medium,
campaign: page.utmParams.utm_campaign,
fbclid: page.query.fbclid,
gclid: page.query.gclid,
sessionReferrer: page.sessionReferrer
};
console.log('Attribution:', attribution);const screen = sdk.context.getScreen();
if (screen.width < 768) {
console.log('Mobile layout');
} else if (screen.width < 1024) {
console.log('Tablet layout');
} else {
console.log('Desktop layout');
}MIT