DOCS LLMs

JavaScript SDK

Official JavaScript/TypeScript SDK for LicenseSeat. Full TypeScript support with auto-generated type definitions.

Current stable release: v0.5.0, published as @licenseseat/js 0.5.0 on npm. Node.js 18 or newer is required.

Installation

Package Managers

# npm
npm install @licenseseat/[email protected]

# yarn
yarn add @licenseseat/[email protected]

# pnpm
pnpm add @licenseseat/[email protected]

CDN (Browser)

<!-- ESM via esm.sh (recommended) -->
<script type="module">
  import LicenseSeat from 'https://esm.sh/@licenseseat/[email protected]';
</script>

<!-- ESM via unpkg -->
<script type="module">
  import LicenseSeat from 'https://unpkg.com/@licenseseat/[email protected]/dist/index.js';
</script>

<!-- ESM via jsDelivr -->
<script type="module">
  import LicenseSeat from 'https://cdn.jsdelivr.net/npm/@licenseseat/[email protected]/dist/index.js';
</script>

The CDN examples pin the reviewed release. Keep an exact version in production instead of allowing a CDN URL to move to a future SDK automatically.

Security Model

  • HTTPS is required. Plain HTTP is accepted only for an explicitly enabled loopback development URL.
  • License keys and installation identifiers are sent in JSON request bodies, not URL paths. Bearer credentials are omitted from public health and signing-key requests.
  • Browser and renderer-process code cannot keep an API key secret from the end user or injected scripts. Use only least-privilege publishable client credentials; enforce product tenancy, seat limits, license state, and valuable authorization on a trusted server.
  • localStorage is attacker-writable and is not a trust anchor. Cached online state is revalidated before it grants entitlements. Persistent offline operation requires Ed25519 public keys pinned through trusted application configuration.
  • Request and response bodies, JSON structure, identifiers, timestamps, retries, redirects, and deadlines are bounded and validated fail-closed.

No client SDK can protect licensing decisions from an attacker who controls the application process. Keep high-value authorization on trusted infrastructure.

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('License status:', status.status);

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
  requestTimeoutMs: 15000,                       // Per-attempt request/body deadline
  maxResponseBytes: 1048576,                     // Request/response JSON size ceiling
  allowInsecureHttp: false,                      // HTTP only for explicit loopback development

  // 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)

  // Telemetry and app info
  telemetryEnabled: true,                       // Attach optional bounded telemetry
  browserHighEntropyTelemetryEnabled: false,    // Opt in to Chromium UA-CH enrichment
  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
  signingPublicKeys: {                        // Trusted keys for offline use after reload
    'key-id-001': 'base64url-encoded-32-byte-public-key'
  },

  // 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 HTTPS API base URL
requestTimeoutMs number 15000 Per-attempt deadline covering headers and the response body
maxResponseBytes number 1048576 Maximum request or response JSON body size
allowInsecureHttp boolean false Permit HTTP only for an explicit loopback development 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)
telemetryEnabled boolean true Attach bounded platform telemetry to supported POST requests
browserHighEntropyTelemetryEnabled boolean false Opt in to in-memory Chromium platform-version, architecture, and model hints
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)
signingPublicKeys object {} Trusted Ed25519 public keys by key ID for offline use after reload
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

// Omit the second argument for default device identification.
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('Activation ID:', result.activation?.id);
// {
//   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('Deactivated activation:', result.activation_id);
// {
//   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('License valid:', result.valid);
// {
//   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', () => {
  console.log('License activated');
});

// 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 at most 7 days offline
  signingPublicKeys: {
    'key-id-001': 'base64url-encoded-32-byte-public-key'
  }
});

How It Works

  1. Online: License validated against server
  2. Activation: Legacy offline token + public key established for the current process
  3. Offline: Cached token verified cryptographically (Ed25519)
  4. Clock Tamper Detection: Prevents users from rolling back system clock

Automatic fallback is limited to transport failures and HTTP 408. Other HTTP responses, malformed responses, identity mismatches, and local cryptographic or protocol failures remain authoritative and cannot be replaced by cached authorization.

For offline authority that survives an application reload, embed or inject the expected public key with signingPublicKeys. A key recovered from localStorage is deliberately ignored because attacker-writable storage cannot establish trust. A key fetched over authenticated HTTPS can verify artifacts in the current process, but it is not a durable trust anchor.

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);
  } 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?.status ?? 'client_error');
}

Note: testAuth() calls the protected, side-effect-free /auth endpoint. Success proves that the configured credential authenticated with the required licenses:validate scope; the public health endpoint is not used as proof of credential validity.

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/[email protected]';

    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().

Telemetry

When telemetryEnabled is true (the default), the SDK includes the following optional telemetry on supported POST requests:

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

Browsers expose reduced values by default. Set browserHighEntropyTelemetryEnabled: true to request bounded Chromium platformVersion, architecture, and model hints. The request never delays a licensing call, successful values remain in this SDK instance's memory, and disabling telemetryEnabled prevents both the high-entropy request and telemetry attachment.

Set telemetryEnabled: false to omit the optional telemetry object. The license key, product context, device/installation identifier, successful request activity, and server-visible source IP remain part of the licensing service. Browser/runtime attributes and persistent fingerprints can be linked device data; disabling telemetry alone does not establish GDPR or marketplace-policy compliance.

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