Skip to content

Latest commit

 

History

History
447 lines (335 loc) · 10.2 KB

File metadata and controls

447 lines (335 loc) · 10.2 KB

Context Plugin

Automatic page and environment context collection for SDK Kit.

Features

  • 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

Installation

npm install @prosdevlab/sdk-kit @prosdevlab/sdk-kit-plugins

Usage

Basic Usage

import { 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 Specific Context

// 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();

Query Parameter Filtering

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 caution

Referrer Tracking

Only 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);    // false

Session Detection

Sessions 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 referrer

Custom session config:

const sdk = new SDK({
  context: {
    sessionCookieName: '_my_session',
    sessionDuration: 3600  // 1 hour (default: 1800 = 30 min)
  }
});

Single Page Applications (SPAs)

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>;
}

Device Detection

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);

Storage Detection

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');
}

Events

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);
});

API

sdk.context.get(): Context

Get full context (lazy, cached).

Returns:

{
  page: PageContext;
  device: DeviceContext;
  screen: ScreenContext;
  environment: EnvironmentContext;
  timestamp: number;
}

sdk.context.getPage(): PageContext

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
}

sdk.context.getDevice(): DeviceContext

Get device context only.

Returns:

{
  type: 'mobile' | 'desktop' | 'unknown';
  userAgent: string;  // Raw UA string
}

sdk.context.getScreen(): ScreenContext

Get screen context only.

Returns:

{
  width: number;   // Screen width
  height: number;  // Screen height
}

sdk.context.getEnvironment(): EnvironmentContext

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?
}

sdk.context.refresh(): void

Clear cache and re-collect context. Use this in SPAs when the route changes.

Configuration

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)
  };
}

Events

Event Payload Description
context:collected Context Emitted when context is first collected
context:refreshed Context Emitted when context is refreshed

Privacy & GDPR

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

Browser Compatibility

  • Modern browsers (Chrome, Firefox, Safari, Edge)
  • Mobile browsers (iOS Safari, Chrome Mobile)
  • Graceful degradation in non-browser environments (Node.js, SSR)

Dependencies

  • Optional: Storage Plugin (for session detection)
    • Without Storage Plugin, every page load is treated as a new session

Design Principles

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

Examples

Analytics SDK

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();

Marketing Attribution

// 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);

Responsive Design

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');
}

License

MIT