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)
// Heartbeat
heartbeatInterval: 300000, // 5 minutes (in ms)
// App Info
appVersion: null, // Your app version (e.g., '2.1.0')
appBuild: null, // Your app build number (e.g., '42')
// 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) |
heartbeatInterval |
number |
300000 |
Standalone heartbeat interval (ms, 0 = disabled) |
appVersion |
string |
null |
Your app version for telemetry |
appBuild |
string |
null |
Your app build number for telemetry |
offlineFallbackEnabled |
boolean |
false |
Enable offline validation |
maxOfflineDays |
number |
0 |
Max offline days (0 = disabled) |
offlineLicenseRefreshInterval |
number |
259200000 |
Legacy 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 fingerprint (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 |
| Heartbeat | ||
heartbeat:success |
{ received_at } |
Heartbeat acknowledged by server |
heartbeat:error |
{ error } |
Heartbeat request failed |
| 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 legacy offline token |
offlineToken:fetched |
{ licenseKey, data } |
Legacy 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.
Note: The current JavaScript SDK offline flow still uses signed offline tokens. Machine files are the newer preferred offline artifact at the API level, but the JavaScript SDK has not migrated to them yet. Treat offline tokens as the current JS implementation detail, not the long-term product direction.
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: Legacy 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 (downloads the legacy token + signing key, caches them)
await sdk.syncOfflineAssets();
// Verify cached offline token (use when offline)
const result = await sdk.verifyCachedOffline();
// { valid: true, offline: true, license: {...}, activation: {...} }
// Get offline token from server
const token = await sdk.getOfflineToken();
// Get signing key
const signingKey = await sdk.getSigningKey('key-id-001');
// Verify a specific token manually
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().
Telemetry
The SDK automatically collects and sends the following telemetry fields on every API call:
| Field | Browser | Node.js | Electron |
|---|---|---|---|
sdk_name |
js |
js |
js |
sdk_version |
Yes | Yes | Yes |
os_name |
Yes | Yes | Yes |
os_version |
Yes | Yes | Yes |
platform |
browser |
node |
electron |
device_model |
Via userAgentData | -- | -- |
device_type |
desktop/phone/tablet |
server |
desktop |
architecture |
Via userAgentData | process.arch |
process.arch |
cpu_cores |
navigator.hardwareConcurrency |
os.cpus().length |
Yes |
memory_gb |
navigator.deviceMemory |
os.totalmem() |
Yes |
locale |
navigator.language |
process.env.LANG |
Yes |
language |
2-letter code from locale | 2-letter code from locale | Yes |
timezone |
Intl.DateTimeFormat |
Intl.DateTimeFormat |
Yes |
screen_resolution |
screen.widthxscreen.height |
-- | Yes |
display_scale |
window.devicePixelRatio |
-- | Yes |
browser_name |
Chrome, Safari, Firefox, Edge | -- | -- |
browser_version |
Detected from UA/brands | -- | -- |
runtime_version |
-- | process.versions.node |
process.versions.electron |
app_version |
From config | From config | From config |
app_build |
From config | From config | From config |
See Telemetry for the full field reference.
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