DOCS LLMs

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

  1. Online: License validated against server
  2. Activation: Offline token + public key automatically cached
  3. Offline: Cached token verified cryptographically (Ed25519)
  4. 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 key
  • license_not_found - License key doesn't exist
  • license_expired - License has expired
  • license_suspended - License is suspended
  • license_revoked - License has been revoked
  • seat_limit_reached - No more seats available
  • device_already_activated - Device is already activated
  • activation_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 deviceId to activate().

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