JavaScript SDK
JavaScript SDK
Official JavaScript/TypeScript SDK for LicenseSeat. Full TypeScript support with auto-generated type definitions.
Installation
Package Managers
# npm
npm install @licenseseat/js
# yarn
yarn add @licenseseat/js
# pnpm
pnpm add @licenseseat/js
CDN (Browser)
<!-- ESM via esm.sh (recommended) -->
<script type="module">
import LicenseSeat from 'https://esm.sh/@licenseseat/js';
</script>
<!-- ESM via unpkg -->
<script type="module">
import LicenseSeat from 'https://unpkg.com/@licenseseat/js/dist/index.js';
</script>
<!-- ESM via jsDelivr -->
<script type="module">
import LicenseSeat from 'https://cdn.jsdelivr.net/npm/@licenseseat/js/dist/index.js';
</script>
For version pinning (recommended for production):
<script type="module">
import LicenseSeat from 'https://esm.sh/@licenseseat/[email protected]';
</script>
Quick Start
JavaScript
import LicenseSeat from '@licenseseat/js';
// Create SDK instance
const sdk = new LicenseSeat({
apiKey: 'pk_live_xxxxxxxx',
productSlug: 'your-product', // Required
debug: true
});
// Activate a license
await sdk.activate('YOUR-LICENSE-KEY');
// Check entitlements (simple boolean)
if (sdk.hasEntitlement('pro')) {
enableProFeatures();
}
// Get current status
const status = sdk.getStatus();
console.log(status);
// { status: 'active', license: '...', entitlements: [...] }
TypeScript
import LicenseSeat, {
type LicenseSeatConfig,
type ValidationResult,
type EntitlementCheckResult,
type LicenseStatus
} from '@licenseseat/js';
const config: LicenseSeatConfig = {
apiKey: 'pk_live_xxxxxxxx',
productSlug: 'your-product',
debug: true
};
const sdk = new LicenseSeat(config);
// Full type inference
const result: ValidationResult = await sdk.validateLicense('LICENSE-KEY');
const status: LicenseStatus = sdk.getStatus();
const hasPro: boolean = sdk.hasEntitlement('pro');
TypeScript users get full type support automatically - the package includes generated .d.ts declaration files.
Configuration
const sdk = new LicenseSeat({
// Required
productSlug: 'your-product', // Your product slug from dashboard
// Required for authenticated operations
apiKey: 'pk_live_xxxxxxxx',
// API Configuration
apiBaseUrl: 'https://licenseseat.com/api/v1', // Default
// Storage
storagePrefix: 'licenseseat_', // localStorage key prefix
// Initialization
autoInitialize: true, // Auto-validate cached license on init
// Auto-Validation
autoValidateInterval: 3600000, // 1 hour (in ms)
// Offline Support
offlineFallbackEnabled: false, // Enable offline validation fallback
maxOfflineDays: 0, // Max days offline (0 = disabled)
offlineLicenseRefreshInterval: 259200000, // 72 hours
maxClockSkewMs: 300000, // 5 minutes
// Network
maxRetries: 3, // Retry attempts for failed requests
retryDelay: 1000, // Initial retry delay (ms)
networkRecheckInterval: 30000, // Check connectivity every 30s when offline
// Debug
debug: false // Enable console logging
});
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
productSlug |
string |
— | Required. Your product slug from the dashboard |
apiKey |
string |
null |
Your publishable API key |
apiBaseUrl |
string |
https://licenseseat.com/api/v1 |
API base URL |
storagePrefix |
string |
licenseseat_ |
Prefix for localStorage keys |
autoInitialize |
boolean |
true |
Auto-initialize on construction |
autoValidateInterval |
number |
3600000 |
Auto-validation interval (ms) |
offlineFallbackEnabled |
boolean |
false |
Enable offline validation |
maxOfflineDays |
number |
0 |
Max offline days (0 = disabled) |
offlineLicenseRefreshInterval |
number |
259200000 |
Offline token refresh (72h) |
maxClockSkewMs |
number |
300000 |
Max clock skew (5 min) |
maxRetries |
number |
3 |
Max API retry attempts |
retryDelay |
number |
1000 |
Base retry delay (exponential backoff) |
networkRecheckInterval |
number |
30000 |
Network check interval when offline |
debug |
boolean |
false |
Enable debug logging |
Core Methods
Activation
// Basic activation (device ID auto-generated)
const result = await sdk.activate('LICENSE-KEY');
// With options
const result = await sdk.activate('LICENSE-KEY', {
deviceId: 'custom-device-id', // Optional: auto-generated if not provided
deviceName: "John's MacBook Pro", // Optional: human-readable device name
metadata: { version: '1.0.0' } // Optional: custom metadata
});
console.log(result);
// {
// license_key: 'LICENSE-KEY',
// device_id: 'web-abc123',
// activated_at: '2024-01-15T10:30:00Z',
// activation: {
// object: 'activation',
// id: 123,
// device_id: 'web-abc123',
// license_key: 'LICENSE-KEY',
// activated_at: '2024-01-15T10:30:00Z',
// license: { ... }
// }
// }
Deactivation
const result = await sdk.deactivate();
console.log(result);
// {
// object: 'deactivation',
// activation_id: 123,
// deactivated_at: '2024-01-15T12:00:00Z'
// }
Validation
const result = await sdk.validateLicense('LICENSE-KEY', {
deviceId: 'device-id' // Optional: required for hardware_locked mode
});
console.log(result);
// {
// valid: true,
// license: {
// key: 'LICENSE-KEY',
// status: 'active',
// mode: 'hardware_locked',
// plan_key: 'pro',
// active_seats: 1,
// seat_limit: 3,
// active_entitlements: [
// { key: 'pro', expires_at: null, metadata: null },
// { key: 'beta', expires_at: '2024-12-31T23:59:59Z', metadata: null }
// ],
// product: { slug: 'your-product', name: 'Your Product' }
// },
// active_entitlements: [...]
// }
Entitlements
Entitlements are optional. A license may have zero entitlements if the associated plan has none configured.
Simple Check (Boolean)
if (sdk.hasEntitlement('pro')) {
enableProFeatures();
}
if (sdk.hasEntitlement('beta')) {
showBetaUI();
}
Detailed Check
const result = sdk.checkEntitlement('pro');
if (result.active) {
console.log('Entitlement:', result.entitlement);
console.log('Expires:', result.entitlement.expires_at);
} else {
console.log('Reason:', result.reason);
// Possible reasons: 'no_license', 'not_found', 'expired'
}
EntitlementCheckResult Type
| Property | Type | Description |
|---|---|---|
active |
boolean |
Whether the entitlement is active |
reason |
string? |
Why inactive: no_license, not_found, expired |
expires_at |
string? |
ISO8601 expiration date |
entitlement |
Entitlement? |
Full entitlement object if active |
Status
Get Current Status
const status = sdk.getStatus();
switch (status.status) {
case 'inactive':
showActivationScreen();
break;
case 'pending':
showLoadingIndicator();
break;
case 'active':
enableFeatures(status.entitlements);
break;
case 'offline-valid':
enableFeatures(status.entitlements);
showOfflineBanner();
break;
case 'invalid':
showErrorScreen(status.message);
break;
case 'offline-invalid':
showRenewalScreen();
break;
}
Status Values
| Status | Description |
|---|---|
inactive |
No license activated |
pending |
License pending validation |
active |
License valid (online) |
offline-valid |
License valid (offline verification) |
invalid |
License invalid |
offline-invalid |
License invalid (offline) |
LicenseStatus Type
| Property | Type | Description |
|---|---|---|
status |
string |
Status value (see above) |
message |
string? |
Status message |
license |
string? |
License key (if active) |
device |
string? |
Device identifier (if active) |
activated_at |
string? |
ISO8601 activation timestamp |
last_validated |
string? |
ISO8601 last validation timestamp |
entitlements |
Entitlement[]? |
Active entitlements |
Events
Subscribe to SDK lifecycle events for reactive UIs.
// Subscribe
const unsubscribe = sdk.on('activation:success', (data) => {
console.log('License activated:', data);
});
// Unsubscribe
unsubscribe();
// or
sdk.off('activation:success', handler);
Available Events
| Event | Data | Description |
|---|---|---|
| Lifecycle | ||
license:loaded |
CachedLicense |
Cached license loaded on init |
sdk:reset |
— | SDK was reset |
sdk:destroyed |
— | SDK was destroyed |
sdk:error |
{ message, error? } |
General SDK error |
| Activation | ||
activation:start |
{ licenseKey, deviceId } |
Activation started |
activation:success |
CachedLicense |
Activation succeeded |
activation:error |
{ licenseKey, error } |
Activation failed |
| Deactivation | ||
deactivation:start |
CachedLicense |
Deactivation started |
deactivation:success |
DeactivationResponse |
Deactivation succeeded |
deactivation:error |
{ error, license } |
Deactivation failed |
| Validation | ||
validation:start |
{ licenseKey } |
Validation started |
validation:success |
ValidationResult |
Online validation succeeded |
validation:failed |
ValidationResult |
Validation failed (invalid) |
validation:error |
{ licenseKey, error } |
Validation error (network) |
validation:offline-success |
ValidationResult |
Offline validation succeeded |
validation:offline-failed |
ValidationResult |
Offline validation failed |
validation:auth-failed |
{ licenseKey, error, cached } |
Auth failed during validation |
| Auto-Validation | ||
autovalidation:cycle |
{ nextRunAt: Date } |
Auto-validation scheduled |
autovalidation:stopped |
— | Auto-validation stopped |
| Network | ||
network:online |
— | Network connectivity restored |
network:offline |
{ error } |
Network connectivity lost |
| Offline Token | ||
offlineToken:fetching |
{ licenseKey } |
Fetching offline token |
offlineToken:fetched |
{ licenseKey, data } |
Offline token fetched |
offlineToken:fetchError |
{ licenseKey, error } |
Fetch failed |
offlineToken:ready |
{ kid, exp_at } |
Offline assets synced |
offlineToken:verified |
{ payload } |
Signature verified |
offlineToken:verificationFailed |
{ payload } |
Signature invalid |
Singleton Pattern
For applications that need a shared SDK instance across modules:
import { configure, getSharedInstance, resetSharedInstance } from '@licenseseat/js';
// Configure once at app startup
configure({
apiKey: 'pk_live_xxxxxxxx',
productSlug: 'your-product'
});
// Use anywhere in your app
const sdk = getSharedInstance();
await sdk.activate('LICENSE-KEY');
// Reset if needed (clears all state)
resetSharedInstance();
Lazy Initialization
By default, the SDK initializes immediately and validates any cached license. To disable this:
const sdk = new LicenseSeat({
apiKey: 'pk_live_xxxxxxxx',
productSlug: 'your-product',
autoInitialize: false // Don't auto-initialize
});
// Later, when ready:
sdk.initialize();
This is useful when you need to:
- Delay network requests until user interaction
- Set up event listeners before initialization
- Control exactly when validation occurs
Offline Support
The SDK supports offline license validation using Ed25519 cryptographic signatures.
Enable Offline Fallback
const sdk = new LicenseSeat({
apiKey: 'pk_live_xxxxxxxx',
productSlug: 'your-product',
offlineFallbackEnabled: true, // Enable offline fallback
maxOfflineDays: 7 // Allow 7 days offline
});
How It Works
- Online: License validated against server
- Activation: Offline token + public key automatically cached
- Offline: Cached token verified cryptographically (Ed25519)
- Clock Tamper Detection: Prevents users from rolling back system clock
Manual Offline Methods
// Sync offline assets after activation
await sdk.syncOfflineAssets();
// Get offline token from server
const token = await sdk.getOfflineToken();
console.log(token);
// {
// object: 'offline_token',
// token: { license_key, product_slug, plan_key, ... },
// signature: { algorithm: 'Ed25519', key_id, value },
// canonical: '...'
// }
// Get signing key
const signingKey = await sdk.getSigningKey('key-id-001');
// Verify signature locally
const isValid = await sdk.verifyOfflineToken(token, signingKey.public_key);
Offline Token Structure
{
object: 'offline_token',
token: {
schema_version: 1,
license_key: 'LICENSE-KEY',
product_slug: 'your-product',
plan_key: 'pro',
mode: 'hardware_locked',
device_id: 'web-abc123',
iat: 1704067200, // Issued at (Unix timestamp)
exp: 1706659200, // Expires at (Unix timestamp)
nbf: 1704067200, // Not before (Unix timestamp)
license_expires_at: null,
kid: 'key-id-001',
entitlements: [
{ key: 'pro', expires_at: null }
],
metadata: {}
},
signature: {
algorithm: 'Ed25519',
key_id: 'key-id-001',
value: 'base64url-encoded-signature'
},
canonical: '{"entitlements":[...],"exp":...}'
}
Offline Validation Result
When offline, validateLicense() returns with offline: true:
const result = await sdk.validateLicense('LICENSE-KEY');
if (result.offline) {
console.log('Validated offline');
}
Error Handling
The SDK exports custom error classes for precise error handling:
import LicenseSeat, {
APIError,
LicenseError,
ConfigurationError,
CryptoError
} from '@licenseseat/js';
try {
await sdk.activate('INVALID-KEY');
} catch (error) {
if (error instanceof APIError) {
console.log('HTTP Status:', error.status);
console.log('Error Code:', error.data?.error?.code);
console.log('Error Message:', error.data?.error?.message);
} else if (error instanceof LicenseError) {
console.log('License error:', error.code);
} else if (error instanceof CryptoError) {
console.log('Crypto error:', error.message);
}
}
Error Types
| Error | Properties | Description |
|---|---|---|
APIError |
status, data |
HTTP request failures |
LicenseError |
code |
License operation failures |
ConfigurationError |
— | SDK misconfiguration |
CryptoError |
— | Cryptographic operation failures |
API Error Format
{
error: {
code: 'license_not_found', // Machine-readable error code
message: 'License not found.', // Human-readable message
details: { ... } // Optional additional details
}
}
Common Error Codes
unauthorized- Invalid or missing API keylicense_not_found- License key doesn't existlicense_expired- License has expiredlicense_suspended- License is suspendedlicense_revoked- License has been revokedseat_limit_reached- No more seats availabledevice_already_activated- Device is already activatedactivation_not_found- Activation doesn't exist (for deactivation)
Utility Methods
Test Authentication
try {
const result = await sdk.testAuth();
console.log('Authenticated:', result.authenticated); // Always true if succeeds
console.log('Healthy:', result.healthy); // API health status
console.log('API Version:', result.api_version); // e.g., '1.0.0'
} catch (error) {
console.error('Auth failed:', error);
}
Note: This tests API connectivity, not API key validity. A successful response means the API is reachable.
Reset SDK
Clears all cached data and stops timers:
sdk.reset();
Destroy SDK
Fully destroys the instance and releases all resources:
sdk.destroy();
// Do not use sdk after this
TypeScript Types
All types are exported from the package:
import type {
LicenseSeatConfig,
ActivationOptions,
ValidationOptions,
ValidationResult,
EntitlementCheckResult,
LicenseStatus,
Entitlement,
CachedLicense,
ActivationResponse,
DeactivationResponse,
OfflineToken
} from '@licenseseat/js';
React Integration
import { useState, useEffect, createContext, useContext } from 'react';
import LicenseSeat from '@licenseseat/js';
// Context
const LicenseContext = createContext(null);
// Provider
export function LicenseProvider({ children, config }) {
const [sdk] = useState(() => new LicenseSeat(config));
const [status, setStatus] = useState(sdk.getStatus());
useEffect(() => {
const events = [
'activation:success',
'deactivation:success',
'validation:success',
'validation:failed',
'validation:offline-success',
'validation:offline-failed'
];
const unsubscribers = events.map(event =>
sdk.on(event, () => setStatus(sdk.getStatus()))
);
return () => {
unsubscribers.forEach(unsub => unsub());
sdk.destroy(); // Clean up on unmount
};
}, [sdk]);
return (
<LicenseContext.Provider value={{ sdk, status }}>
{children}
</LicenseContext.Provider>
);
}
// Hook
export function useLicense() {
return useContext(LicenseContext);
}
// Usage
function App() {
return (
<LicenseProvider config={{ apiKey: 'pk_live_xxxxxxxx', productSlug: 'your-product' }}>
<MainApp />
</LicenseProvider>
);
}
function MainApp() {
const { sdk, status } = useLicense();
if (status.status === 'active') {
return <Dashboard hasPro={sdk.hasEntitlement('pro')} />;
}
return <ActivationForm sdk={sdk} />;
}
Browser Usage (No Build Tools)
<!DOCTYPE html>
<html>
<head>
<title>LicenseSeat Demo</title>
</head>
<body>
<input id="license-key" placeholder="Enter license key" />
<button id="activate-btn">Activate</button>
<div id="status"></div>
<script type="module">
import LicenseSeat from 'https://esm.sh/@licenseseat/js';
const sdk = new LicenseSeat({
apiKey: 'pk_live_xxxxxxxx',
productSlug: 'your-product',
debug: true
});
// Display current status
function updateUI() {
const status = sdk.getStatus();
document.getElementById('status').textContent =
`Status: ${status.status}`;
}
// Listen for changes
sdk.on('activation:success', updateUI);
sdk.on('validation:success', updateUI);
// Handle activation
document.getElementById('activate-btn').onclick = async () => {
const key = document.getElementById('license-key').value;
try {
await sdk.activate(key);
alert('License activated!');
} catch (e) {
alert('Activation failed: ' + e.message);
}
};
updateUI();
</script>
</body>
</html>
Node.js Usage
The SDK is designed for browsers but works in Node.js with polyfills:
// Required polyfills for Node.js
const storage = {};
globalThis.localStorage = {
getItem(key) { return Object.prototype.hasOwnProperty.call(storage, key) ? storage[key] : null; },
setItem(key, value) { storage[key] = String(value); },
removeItem(key) { delete storage[key]; },
clear() { for (const key in storage) delete storage[key]; },
};
const originalKeys = Object.keys;
Object.keys = function(obj) {
if (obj === globalThis.localStorage) return originalKeys(storage);
return originalKeys(obj);
};
globalThis.document = { createElement: () => ({ getContext: () => null }), querySelector: () => null };
globalThis.window = { navigator: {}, screen: {} };
globalThis.navigator = { userAgent: 'Node.js', language: 'en', hardwareConcurrency: 4 };
// Now import the SDK
const { default: LicenseSeat } = await import('@licenseseat/js');
Note: In Node.js, device fingerprinting uses fallback values. For consistent device identification, pass an explicit
deviceIdtoactivate().
Platform Support
| Platform | Version | Notes |
|---|---|---|
| Chrome | 80+ | Full support |
| Firefox | 75+ | Full support |
| Safari | 14+ | Full support |
| Edge | 80+ | Full support |
| Node.js | 18+ | Requires polyfills |
| Electron | Latest | Full support |
Next Steps
- Swift SDK - For Apple platforms
- Offline Licensing - Air-gapped validation
- API Reference - Direct API access