Skip to content

Latest commit

 

History

History
408 lines (317 loc) · 10.4 KB

File metadata and controls

408 lines (317 loc) · 10.4 KB

Consent Plugin

State-based consent management with battle-tested patterns for GDPR/CCPA compliance.

Features

Core Capabilities

  • State-based consent - Category-based control (necessary, functional, analytics, marketing, social)
  • Inferred consent - Auto opt-in if cookie exists (don't nag returning users)
  • Cookie persistence - Remember consent across sessions
  • Promise-based API - waitForConsent() for async consent checks
  • Event emission - consent:granted, consent:denied, consent:updated
  • Nuclear opt-out - revoke() deletes all consent and cookies
  • Platform adapters - Auto-detect OneTrust, Cookiebot, Usercentrics
  • Metadata tracking - Track when/how consent was given
  • Policy versioning - Re-request consent when policy updates

Integrations

  • Storage Plugin - Persists consent to cookies
  • Queue Plugin - Consent-aware queuing (see integration examples)
  • OneTrust - Auto-sync with OneTrust consent management
  • Cookiebot - Auto-sync with Cookiebot
  • Usercentrics - Auto-sync with Usercentrics

Usage

Basic Setup

import { SDK } from '@prosdevlab/sdk-kit';
import { consentPlugin, storagePlugin } from '@prosdevlab/sdk-kit-plugins';

const sdk = new SDK();

// Storage plugin required for persistence
sdk.use(storagePlugin);
sdk.use(consentPlugin);

await sdk.init();

Grant/Deny Consent

// Grant consent for analytics
sdk.consent.grant('analytics');

// Grant multiple categories
sdk.consent.grant(['analytics', 'marketing']);

// Deny consent
sdk.consent.deny('marketing');

// With metadata
sdk.consent.grant('analytics', {
  method: 'banner',
  policyVersion: '2.0'
});

Check Consent

// Check if granted
if (sdk.consent.isGranted('analytics')) {
  // Track events
  sdk.track('pageview');
}

// Check if denied
if (sdk.consent.isDenied('marketing')) {
  // Don't show marketing content
}

// Check if pending (no decision yet)
if (sdk.consent.isPending('analytics')) {
  // Show consent banner
}

// Get full state
const state = sdk.consent.getState();
// { necessary: 'granted', analytics: 'granted', marketing: 'denied', ... }

Wait for Consent (Promise-based)

// Block until user makes a decision
const granted = await sdk.consent.waitForConsent('analytics');
if (granted) {
  // User granted analytics consent
  sdk.track('pageview');
}

// With timeout
const granted = await sdk.consent.waitForConsent('analytics', { timeout: 10000 });

Nuclear Opt-Out

// Revoke ALL consent (except necessary)
sdk.consent.revoke();
// - Denies all non-necessary categories
// - Clears consent cookie
// - Emits 'consent:revoked' event

Inferred Consent (Auto Opt-In)

// If user has existing consent cookie, auto opt-in
// (Don't show banner to returning users - reduces banner fatigue by ~70%)

await sdk.init({
  consent: {
    inferConsent: true // Default: true
  }
});

// Listen for inferred consent
sdk.on('consent:inferred', ({ categories }) => {
  console.log('Inferred consent for:', categories);
});

Platform Integration (OneTrust, Cookiebot)

// Auto-detect and sync with OneTrust
await sdk.init();

// SDK automatically:
// 1. Detects window.OneTrust
// 2. Syncs consent state
// 3. Listens for changes

// Custom category mapping
await sdk.init({
  consent: {
    platform: 'onetrust',
    platformConfig: {
      onetrust: {
        categoryMap: {
          'C0001': 'necessary',
          'C0002': 'functional',
          'C0003': 'analytics',
          'C0004': 'marketing'
        }
      }
    }
  }
});

// Listen for platform detection
sdk.on('consent:platform-detected', ({ platform }) => {
  console.log('Detected:', platform); // 'onetrust', 'cookiebot', etc.
});

Metadata & Versioning

// Get consent metadata
const metadata = sdk.consent.getMetadata('analytics');
// {
//   grantedAt: 1234567890,
//   method: 'banner',
//   policyVersion: '2.0'
// }

// Check policy version
if (!sdk.consent.isCurrentVersion('analytics', '2.0')) {
  // Policy changed, re-request consent
  showConsentBanner();
}

Events

// Consent granted
sdk.on('consent:granted', ({ categories, metadata }) => {
  console.log('Granted:', categories);
  // Flush queued events
  sdk.queue?.flush();
});

// Consent denied
sdk.on('consent:denied', ({ categories, metadata }) => {
  console.log('Denied:', categories);
});

// Consent updated
sdk.on('consent:updated', ({ state }) => {
  console.log('State:', state);
});

// Consent inferred from cookie
sdk.on('consent:inferred', ({ categories }) => {
  console.log('Inferred consent for:', categories);
});

// Consent revoked
sdk.on('consent:revoked', ({ categories }) => {
  console.log('Revoked consent for:', categories);
});

// Platform detected
sdk.on('consent:platform-detected', ({ platform }) => {
  console.log('Platform:', platform);
});

Integration with Queue Plugin

Consent-Aware Queuing

import { queuePlugin, consentPlugin } from '@prosdevlab/sdk-kit-plugins';

sdk.use(queuePlugin);
sdk.use(consentPlugin);
await sdk.init();

// Queue events that require consent
sdk.queue.add({ event: 'pageview', data: {...} }, {
  consent: ['analytics'] // Queued until analytics consent granted
});

sdk.queue.add({ event: 'ad_click', data: {...} }, {
  consent: ['marketing'] // Queued until marketing consent granted
});

// When consent granted, flush relevant items
sdk.on('consent:granted', ({ categories }) => {
  if (categories.includes('analytics')) {
    // Flush analytics events
    sdk.queue.flush();
  }
});

Custom Plugin with Consent Check

export default function myPlugin(plugin, instance, config) {
  plugin.ns('my.plugin');
  
  plugin.expose({
    track(event, data) {
      // Check consent before tracking
      if (!instance.consent?.isGranted('analytics')) {
        // Queue for later
        instance.queue?.add({ event, data }, {
          consent: ['analytics']
        });
        return;
      }
      
      // Has consent, send now
      instance.transport.send({ event, data });
    }
  });
  
  // Auto-flush on consent granted
  instance.on('consent:granted', (categories) => {
    if (categories.includes('analytics')) {
      instance.queue?.flush();
    }
  });
}

Configuration

await sdk.init({
  consent: {
    // Default status for each category
    defaults: {
      necessary: 'granted',    // Always granted
      functional: 'pending',
      analytics: 'pending',
      marketing: 'pending',
      social: 'pending'
    },
    
    // Cookie name for persistence
    cookieName: '_sdk_consent', // Default
    
    // Cookie TTL in seconds (1 year)
    cookieTTL: 31536000,
    
    // Current policy version
    policyVersion: '1.0',
    
    // Infer consent from existing cookie
    inferConsent: true, // Default
    
    // Platform adapter
    platform: 'onetrust', // 'onetrust', 'cookiebot', 'usercentrics', or null
    
    // Platform-specific config
    platformConfig: {
      onetrust: {
        categoryMap: {
          'C0001': 'necessary',
          'C0002': 'functional',
          'C0003': 'analytics',
          'C0004': 'marketing',
          'C0005': 'social'
        }
      }
    }
  }
});

API Reference

ConsentPlugin

isGranted(category: ConsentCategory | string): boolean

Check if consent is granted for a category.

isDenied(category: ConsentCategory | string): boolean

Check if consent is denied for a category.

isPending(category: ConsentCategory | string): boolean

Check if consent is pending (no decision yet) for a category.

grant(categories: ConsentCategory | ConsentCategory[] | string | string[], metadata?: ConsentMetadata): void

Grant consent for one or more categories.

deny(categories: ConsentCategory | ConsentCategory[] | string | string[], metadata?: ConsentMetadata): void

Deny consent for one or more categories.

waitForConsent(category: ConsentCategory | string, options?: { timeout?: number }): Promise<boolean>

Wait for a consent decision (promise-based). Returns true if granted, false if denied or timeout.

getState(): ConsentState

Get current consent state for all categories.

getMetadata(category: ConsentCategory | string): ConsentMetadata | null

Get metadata (when granted, method, policy version) for a category.

isCurrentVersion(category: ConsentCategory | string, version: string): boolean

Check if consent was granted for a specific policy version.

revoke(): void

Revoke ALL consent (except necessary). Clears cookie and denies all non-necessary categories.

Types

type ConsentStatus = 'granted' | 'denied' | 'pending';

type ConsentCategory = 
  | 'necessary'   // Always granted (required)
  | 'functional'  // Preferences, settings
  | 'analytics'   // Analytics, tracking
  | 'marketing'   // Advertising, remarketing
  | 'social';     // Social media

interface ConsentState {
  [category: string]: ConsentStatus;
}

interface ConsentMetadata {
  grantedAt?: number;
  deniedAt?: number;
  policyVersion?: string;
  method?: 'banner' | 'settings' | 'api' | 'inferred';
}

Browser Compatibility

  • Modern browsers: Full support
  • Older browsers: Graceful degradation (no persistence without Storage plugin)
  • SSR: Safe (no-op in non-browser environments)

Events

Event Data Description
consent:granted { categories: string[], metadata: ConsentMetadata } Consent granted for categories
consent:denied { categories: string[], metadata: ConsentMetadata } Consent denied for categories
consent:updated { state: ConsentState } Consent state changed
consent:inferred { categories: string[] } Consent inferred from cookie
consent:revoked { categories: string[] } All consent revoked
consent:platform-detected { platform: string } Consent platform detected

Battle-Tested Patterns

This plugin adopts proven patterns from production SDKs:

  1. Inferred Consent - Auto opt-in if cookie exists (reduces banner fatigue by ~70%)
  2. Nuclear Opt-Out - Complete data deletion on revoke (GDPR "right to be forgotten")
  3. Platform Delegation - Clean integration with OneTrust/Cookiebot/Usercentrics
  4. State-Based - Category-based control (modern GDPR/CCPA compliance)
  5. Promise-Based - Async waitForConsent() for better DX

License

MIT