Skip to content

CleverTap/clevertap-web-zeropii-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CleverTap ZeroPii SDK for Web

Overview

CleverTap ZeroPii SDK provides a secure way to tokenize Personally Identifiable Information (PII) in your web applications. By replacing sensitive data with format-preserving tokens, you can minimize the exposure of sensitive information while maintaining data utility. This SDK provides a Promise-based TypeScript API for seamless integration.

Features

  • Type-Safe Tokenization: Support for String, Int, Long, Float, Double, Boolean
  • Batch Operations: Process up to 1,000 values in a single request
  • Token Caching: Automatic OAuth token caching with 30-second expiry buffer
  • AES-256-GCM Encryption: End-to-end encryption with automatic 419 fallback
  • Pluggable Retry Policy: Customize retry behavior or use built-in exponential backoff
  • TypeScript Support: Full type safety and IntelliSense support
  • Zero Dependencies: Uses native browser APIs (Web Crypto, fetch)

Requirements

System Requirements

  • Node.js: 16.x or higher (for development)
  • npm: 8.x or higher
  • TypeScript: 5.0 or higher (optional, for TypeScript projects)

Browser Support

  • Chrome: 80+
  • Firefox: 75+
  • Safari: 14+
  • Edge: 80+

The SDK requires browsers with support for:

  • Web Crypto API (for AES-256-GCM encryption)
  • Fetch API
  • ES2020+ features (Promises, async/await)

Dependencies

No runtime dependencies. The SDK uses native browser APIs exclusively.


Installation

npm install clevertap-web-zeropii-sdk

Quick Start

1. Implement Token Provider

The SDK requires you to implement an AccessTokenProvider to fetch OAuth tokens:

import { AccessTokenProvider, AccessTokenInfo } from 'clevertap-web-zeropii-sdk';

class MyTokenProvider implements AccessTokenProvider {
  async fetchToken(): Promise<AccessTokenInfo> {
    // Fetch token from your OAuth server
    const response = await fetch('https://auth.example.com/oauth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: 'your-client-id',
        client_secret: 'your-client-secret'
      })
    });

    const data = await response.json();

    return {
      token: data.access_token,
      expiresInSeconds: data.expires_in  // Required for automatic refresh
    };
  }
}

2. Initialize SDK

import { ZeroPiiSDK, LogLevel } from 'clevertap-web-zeropii-sdk';

const sdk = ZeroPiiSDK.initialize({
  tokenProvider: new MyTokenProvider(),
  apiUrl: 'https://zeropii-api.clevertap.com/',
  logLevel: LogLevel.INFO,  // Optional, default: OFF
  retryPolicy: myCustomRetryPolicy  // Optional, default: built-in exponential backoff
});

3. Tokenize Data

// Single value tokenization
const result = await sdk.tokenizeString('john.doe@example.com');

if (result.type === 'success') {
  console.log('Token:', result.token);
  console.log('Exists:', result.exists);
  console.log('Newly Created:', result.newlyCreated);
} else {
  console.error('Error:', result.message);
}

// Batch tokenization
const batchResult = await sdk.batchTokenizeStringValues([
  'user1@example.com',
  'user2@example.com',
  'user3@example.com'
]);

if (batchResult.type === 'success') {
  console.log('Processed:', batchResult.summary.processedCount);
  console.log('Results:', batchResult.results);
}

API Reference

Initialization

ZeroPiiSDK.initialize(config: ZeroPiiConfig): ZeroPiiSDK

Initialize the SDK singleton. Must be called before any operations.

interface ZeroPiiConfig {
  tokenProvider: AccessTokenProvider;  // Required: OAuth token provider
  apiUrl: string;                      // Required: Vault API base URL
  logLevel?: LogLevel;                 // Optional: OFF | ERROR | INFO | DEBUG | VERBOSE
  retryPolicy?: RetryPolicy;           // Optional: Custom retry policy
}

ZeroPiiSDK.getInstance(): ZeroPiiSDK

Get the singleton instance (must call initialize() first).

const sdk = ZeroPiiSDK.getInstance();

Single Value Tokenization

All methods return Promise<TokenizeResult>:

type TokenizeResult =
  | {
      type: 'success';
      token: string;
      exists: boolean;        // Token already existed
      newlyCreated: boolean;  // Token created in this request
      dataType: string | null;
    }
  | {
      type: 'error';
      message: string;
      code?: ErrorCode;
      statusCode?: number;    // HTTP status code (undefined for network errors)
    };

Type-Specific Methods

// String
await sdk.tokenizeString(value: string): Promise<TokenizeResult>

// Integer
await sdk.tokenizeInt(value: number): Promise<TokenizeResult>

// Long (64-bit integer)
await sdk.tokenizeLong(value: number): Promise<TokenizeResult>

// Float
await sdk.tokenizeFloat(value: number): Promise<TokenizeResult>

// Double
await sdk.tokenizeDouble(value: number): Promise<TokenizeResult>

// Boolean
await sdk.tokenizeBool(value: boolean): Promise<TokenizeResult>

Generic Method (Convenience)

await sdk.tokenize(value: string | number | boolean): Promise<TokenizeResult>

Example:

// Dynamic form data
const formData: Record<string, string | number | boolean> = getFormData();

for (const [key, value] of Object.entries(formData)) {
  const result = await sdk.tokenize(value);
  // Handle result
}

Batch Tokenization

Process up to 1,000 values in a single request. Returns Promise<BatchTokenizeResult>:

type BatchTokenizeResult =
  | {
      type: 'success';
      results: BatchTokenizeItemResult[];
      summary: {
        processedCount: number;
        existingCount: number;
        newlyCreatedCount: number;
      };
    }
  | {
      type: 'error';
      message: string;
      statusCode?: number;    // HTTP status code (undefined for network errors)
    };

interface BatchTokenizeItemResult {
  originalValue: string;
  token: string;
  exists: boolean;
  newlyCreated: boolean;
  dataType: string | null;
}

Batch Methods

await sdk.batchTokenizeStringValues(values: string[]): Promise<BatchTokenizeResult>
await sdk.batchTokenizeIntValues(values: number[]): Promise<BatchTokenizeResult>
await sdk.batchTokenizeLongValues(values: number[]): Promise<BatchTokenizeResult>
await sdk.batchTokenizeFloatValues(values: number[]): Promise<BatchTokenizeResult>
await sdk.batchTokenizeDoubleValues(values: number[]): Promise<BatchTokenizeResult>
await sdk.batchTokenizeBoolValues(values: boolean[]): Promise<BatchTokenizeResult>

Example:

const emails = ['user1@example.com', 'user2@example.com', 'user3@example.com'];
const result = await sdk.batchTokenizeStringValues(emails);

if (result.type === 'success') {
  console.log(`Processed: ${result.summary.processedCount}`);
  console.log(`Existing: ${result.summary.existingCount}`);
  console.log(`New: ${result.summary.newlyCreatedCount}`);

  result.results.forEach(item => {
    console.log(`${item.originalValue} -> ${item.token}`);
  });
}

Token Provider

AccessTokenProvider Interface

interface AccessTokenProvider {
  fetchToken(): Promise<AccessTokenInfo>;
}

interface AccessTokenInfo {
  token: string;            // OAuth bearer token
  expiresInSeconds: number; // Token lifetime (e.g., 300 for 5 minutes)
}

Important: The expiresInSeconds field is required for automatic token caching and refresh.

Token Caching Behavior

  • Tokens are cached automatically
  • Tokens expire 30 seconds before actual expiration (safety buffer)
  • Tokens are refreshed automatically when expired
  • No manual token management needed

Example Flow:

  1. First call: fetchToken() called, token cached
  2. Subsequent calls (within expiry): Cached token used
  3. After expiry - 30s: fetchToken() called again, new token cached

Configuration

Log Levels

enum LogLevel {
  OFF = 0,      // No logging
  ERROR = 1,    // Errors only
  INFO = 2,     // Info + errors
  DEBUG = 3,    // Debug + info + errors
  VERBOSE = 4   // All logs including network details
}

Retry Policy

RetryPolicy Interface

interface RetryPolicy {
  /**
   * Determines whether the SDK should retry a failed request.
   * @param attempt 0-based attempt index. 0 = after first failure (before first retry).
   * @param httpStatusCode HTTP status code of failed response. undefined = network error.
   * @return true to retry, false to stop and deliver the error.
   */
  shouldRetry(attempt: number, httpStatusCode?: number): boolean;

  /**
   * Returns how long to wait before the next retry attempt.
   * Only called when shouldRetry() returns true.
   * @param attempt 0-based attempt index.
   * @return delay in milliseconds. Return 0 for immediate retry.
   */
  retryDelayMs(attempt: number): number;
}

Implement this interface to customize retry behavior for your application.

Note: 401 Unauthorized responses bypass RetryPolicy entirely — the SDK automatically refreshes the token and retries immediately (no delay).

Default Behavior

If no custom retryPolicy is provided, the SDK uses a built-in exponential backoff policy:

  • Retries on: Server errors (500, 502, 503, 504), rate limiting (429), and network errors (undefined status code)
  • Does not retry on: Client errors (4xx except 401, which is handled internally)
  • Exponential backoff delays: 2s, 4s, 8s, …
  • Maximum retries: 1 by default

Custom Retry Policy Example

Here's an example of a linear retry policy that retries up to 3 times with a fixed 2-second delay:

import { RetryPolicy } from 'clevertap-web-zeropii-sdk';

class LinearRetryPolicy implements RetryPolicy {
  private readonly maxRetries: number;
  private readonly retryableStatusCodes = new Set([500, 502, 503, 504, 429]);

  constructor(maxRetries: number = 3) {
    this.maxRetries = maxRetries;
  }

  shouldRetry(attempt: number, httpStatusCode?: number): boolean {
    if (attempt >= this.maxRetries) {
      return false;
    }

    // Retry on network errors (undefined) or server errors
    return httpStatusCode === undefined || this.retryableStatusCodes.has(httpStatusCode);
  }

  retryDelayMs(attempt: number): number {
    return 2000; // Fixed 2-second delay
  }
}

// Use it during initialization
const sdk = ZeroPiiSDK.initialize({
  tokenProvider: new MyTokenProvider(),
  apiUrl: 'https://zeropii-api.clevertap.com/',
  retryPolicy: new LinearRetryPolicy(3)
});

No-Retry Policy Example

To disable retries entirely:

class NoRetryPolicy implements RetryPolicy {
  shouldRetry(attempt: number, httpStatusCode?: number): boolean {
    return false; // Never retry
  }

  retryDelayMs(attempt: number): number {
    return 0; // Not used since shouldRetry always returns false
  }
}

Encryption

Encryption is always enabled and uses the following:

  • Algorithm: AES-256-GCM
  • IV: 12-byte random nonce per request
  • Tag: 128-bit authentication tag
  • 419 Fallback: Automatically disables encryption if server doesn't support it

API request and response payloads are encrypted in transit for enhanced security.

Security Note: Session keys are transmitted over HTTPS. Always use HTTPS for your API endpoint to ensure secure key exchange. The SDK enforces encryption of the payload data, but relies on HTTPS for transport-layer security of the session key.


Error Handling

All operations use discriminated union results for type-safe error handling:

const result = await sdk.tokenizeString('value');

// Type guard
if (result.type === 'success') {
  // TypeScript knows result has: token, exists, newlyCreated, dataType
  console.log(result.token);
} else {
  // TypeScript knows result has: message, code?, statusCode?
  console.error(result.message);

  // statusCode is present for API errors, undefined for network/SDK errors
  if (result.statusCode) {
    console.error(`HTTP ${result.statusCode}`);
  }
}

Error Codes

enum ErrorCode {
  VALIDATION_EMPTY_VALUE = 'VALIDATION_EMPTY_VALUE',
  VALIDATION_UNSUPPORTED_TYPE = 'VALIDATION_UNSUPPORTED_TYPE',
  AUTH_TOKEN_FETCH_FAILED = 'AUTH_TOKEN_FETCH_FAILED',
  AUTH_INVALID_CREDENTIALS = 'AUTH_INVALID_CREDENTIALS',
  NETWORK_ERROR = 'NETWORK_ERROR',
  NETWORK_TIMEOUT = 'NETWORK_TIMEOUT',
  API_TOKENIZE_FAILED = 'API_TOKENIZE_FAILED',
  API_ENCRYPTION_NOT_SUPPORTED = 'API_ENCRYPTION_NOT_SUPPORTED',
  ENCRYPTION_FAILED = 'ENCRYPTION_FAILED',
  DECRYPTION_FAILED = 'DECRYPTION_FAILED',
  TYPE_CONVERSION_FAILED = 'TYPE_CONVERSION_FAILED',
  UNKNOWN_ERROR = 'UNKNOWN_ERROR'
}

Development

Build

npm install
npm run build

Outputs:

  • dist/esm/ - ES modules
  • dist/cjs/ - CommonJS
  • dist/umd/ - UMD (browser)

Demo

npm run demo

Opens demo at http://localhost:3000

Demo Configuration

The demo application is included in the demo/ directory. To run it:

npm run demo

Configure your OAuth credentials in the demo UI or create a token provider:

// Create token provider for OAuth2 client credentials flow
class DemoTokenProvider implements AccessTokenProvider {
  async fetchToken(): Promise<AccessTokenInfo> {
    const response = await fetch(
      'YOUR_AUTH_URL/protocol/openid-connect/token',
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
          grant_type: 'client_credentials',
          client_id: 'YOUR_CLIENT_ID',
          client_secret: 'YOUR_CLIENT_SECRET'
        })
      }
    );

    const data = await response.json();
    return {
      token: data.access_token,
      expiresInSeconds: data.expires_in
    };
  }
}

const sdk = ZeroPiiSDK.initialize({
  tokenProvider: new DemoTokenProvider(),
  apiUrl: 'YOUR_API_URL',
  logLevel: LogLevel.DEBUG
});

Architecture

Design Patterns

  • Singleton Pattern: One SDK instance per application
  • Repository Pattern: Separation of data access (AuthRepository, TokenRepository)
  • Strategy Pattern: Encryption with automatic 419 fallback
  • Factory Pattern: Type converters and encryption strategies
  • Provider Pattern: Dependency injection for token provider

Project Structure

src/
├── core/
│   └── ZeroPiiSDK.ts                 # Main SDK class
├── repository/
│   ├── AuthRepository.ts           # Token caching with 30s expiry buffer
│   └── TokenRepository.ts          # Tokenization logic
├── network/
│   ├── NetworkProvider.ts          # HTTP client
│   ├── TokenizationApi.ts          # API endpoints
│   └── RetryHandler.ts             # Retry logic
├── encryption/
│   ├── EncryptionManager.ts        # Encryption orchestration
│   ├── NetworkEncryptionManager.ts # AES-GCM implementation
│   ├── EncryptionStrategy.ts       # Strategy pattern
│   └── AESGCMCrypt.ts              # Web Crypto API wrapper
├── types/
│   ├── config.ts                   # Configuration types
│   ├── models.ts                   # API models
│   ├── results.ts                  # Result types
│   └── errors.ts                   # Error types
└── utils/
    ├── Logger.ts                   # Logging utility
    └── TypeConverter.ts            # Type conversion registry

TypeScript

Full TypeScript support with complete type definitions:

import {
  ZeroPiiSDK,
  ZeroPiiConfig,
  AccessTokenProvider,
  AccessTokenInfo,
  TokenizeResult,
  BatchTokenizeResult,
  RetryPolicy,
  LogLevel,
  ErrorCode
} from 'clevertap-web-zeropii-sdk';

Migration from Earlier Versions

Breaking Changes (v2.0)

  1. reset() method removed - Singleton cannot be reset
  2. retryConfig removed - Retry settings now hardcoded (maxRetries=1)
  3. OAuth credentials removed - Use AccessTokenProvider pattern instead
  4. Cache methods removed - Token caching now automatic (access tokens only)
  5. expiresInSeconds required - Must be provided in AccessTokenInfo

Migration Guide

Before (v1.x):

ZeroPiiSDK.initialize({
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET',
  apiUrl: 'https://api.example.com/',
  authUrl: 'https://auth.example.com/',
  retryConfig: { maxRetries: 3 },
  cacheConfig: { enabled: true }
});

ZeroPiiSDK.reset(); // Available
sdk.clearCache(); // Available

After (v2.0):

class MyTokenProvider implements AccessTokenProvider {
  async fetchToken(): Promise<AccessTokenInfo> {
    // Fetch from your OAuth server
    return {
      token: 'access_token',
      expiresInSeconds: 300  // Required
    };
  }
}

ZeroPiiSDK.initialize({
  tokenProvider: new MyTokenProvider(),
  apiUrl: 'https://api.example.com/'
  // No authUrl, retryConfig, or cacheConfig
});

// ZeroPiiSDK.reset() - Removed
// sdk.clearCache() - Removed (automatic caching)

Security Best Practices

Production Deployment

When deploying this SDK to production, follow these security guidelines:

1. Always Use HTTPS

  • Required: Your API endpoint MUST use HTTPS
  • Session keys are transmitted over the network and require transport-layer encryption
  • Never use HTTP for production deployments

2. Secure Credential Management

  • Never hardcode OAuth credentials in your application code
  • Use environment variables or secure credential management services (e.g., AWS Secrets Manager, Azure Key Vault)
  • Implement proper access controls for credential storage
  • Rotate credentials regularly

3. Configure Security Headers

Implement the following HTTP security headers in your application:

Content-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self' https://your-api-domain.com
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

4. Production Log Levels

  • Recommended: Use LogLevel.ERROR or LogLevel.OFF in production
  • Never use LogLevel.DEBUG or LogLevel.VERBOSE in production as they may log sensitive data
  • Log data is automatically redacted, but limiting log verbosity adds an extra layer of security
const sdk = ZeroPiiSDK.initialize({
  tokenProvider: new MyTokenProvider(),
  apiUrl: 'https://api.example.com/',
  logLevel: LogLevel.ERROR  // Production recommendation
});

5. Token Provider Security

  • Implement proper error handling in your AccessTokenProvider
  • Never log or expose OAuth tokens
  • Validate token responses before returning them
  • Consider implementing token refresh retry logic with backoff

6. CDN Usage and Subresource Integrity (SRI)

If loading the SDK from a CDN, use Subresource Integrity hashes to ensure the file hasn't been tampered with:

<script src="https://cdn.example.com/zeropii-sdk@1.0.0/index.js"
        integrity="sha384-[HASH]"
        crossorigin="anonymous"></script>

Generate SRI hashes using: https://www.srihash.org/ or openssl dgst -sha384 -binary <file> | openssl base64 -A

7. Input Validation

While the SDK performs input validation, implement additional validation in your application:

  • Validate data before sending to tokenization
  • Sanitize user inputs to prevent injection attacks
  • Enforce business logic constraints (e.g., email format, phone number format)

8. Rate Limiting

  • Implement client-side rate limiting to prevent accidental DoS
  • Monitor API usage and set up alerts for unusual patterns
  • Consider implementing request throttling

9. Error Handling

  • Never expose detailed error messages to end users
  • Log errors securely for debugging
  • Implement user-friendly error messages that don't leak sensitive information

10. Regular Updates

  • Keep the SDK updated to the latest version
  • Regularly run npm audit to check for vulnerabilities
  • Subscribe to security advisories for dependencies

Vulnerability Reporting

If you discover a security vulnerability, please report it responsibly:

  • Email: security@clevertap.com
  • Do not open public GitHub issues for security vulnerabilities
  • Include detailed steps to reproduce the issue

License

MIT

Contributing

Issues and pull requests welcome at github.com/clevertap/clevertap-web-zeropii-sdk

Support

For questions or issues:

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors