clevertap-web-zeropii-sdk
v1.0.0
Published
CleverTap ZeroPii SDK for Web - Tokenization SDK for PII data
Maintainers
Readme
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-sdkQuick 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('[email protected]');
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([
'[email protected]',
'[email protected]',
'[email protected]'
]);
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 = ['[email protected]', '[email protected]', '[email protected]'];
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:
- First call:
fetchToken()called, token cached - Subsequent calls (within expiry): Cached token used
- 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
RetryPolicyentirely — 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 buildOutputs:
dist/esm/- ES modulesdist/cjs/- CommonJSdist/umd/- UMD (browser)
Demo
npm run demoOpens demo at http://localhost:3000
Demo Configuration
The demo application is included in the demo/ directory. To run it:
npm run demoConfigure 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 registryTypeScript
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)
reset()method removed - Singleton cannot be resetretryConfigremoved - Retry settings now hardcoded (maxRetries=1)- OAuth credentials removed - Use
AccessTokenProviderpattern instead - Cache methods removed - Token caching now automatic (access tokens only)
expiresInSecondsrequired - Must be provided inAccessTokenInfo
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(); // AvailableAfter (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=block4. Production Log Levels
- Recommended: Use
LogLevel.ERRORorLogLevel.OFFin production - Never use
LogLevel.DEBUGorLogLevel.VERBOSEin 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/[email protected]/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 auditto check for vulnerabilities - Subscribe to security advisories for dependencies
Vulnerability Reporting
If you discover a security vulnerability, please report it responsibly:
- Email: [email protected]
- 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:
- Open an issue on GitHub
- Contact CleverTap support at [email protected]
