JSPM

@licensehub/license-client

1.0.7
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 2
  • Score
    100M100P100Q45862F
  • License SEE LICENSE IN LICENSE

TypeScript license management client for protecting and monetizing proprietary software

Package Exports

  • @licensehub/license-client
  • @licensehub/license-client/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@licensehub/license-client) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

@licensehub/license-client

A comprehensive TypeScript license management client for protecting and monetizing proprietary software. This client provides secure license validation, activation management, and offline capabilities through intelligent caching and JWT-based verification.

πŸš€ What This Package Does

The License Client is a robust solution that enables you to:

  • πŸ›‘οΈ Advanced Security - Protects against tampering with a hash-based integrity cache.
  • πŸ”„ Robust Validation - Periodically re-validates licenses with the server to prevent abuse, even in offline scenarios.
  • ⚑ Work Offline - Validate licenses without internet connectivity using a secure, integrity-protected cache.
  • πŸ” Cryptographic Verification - Uses JWT for secure, offline license validation.
  • πŸ’° Monetize Your Code - Implement flexible licensing models (perpetual, subscription, trial).
  • 🎯 Control Usage - Limit activations per license and track usage across devices.

πŸ“¦ Installation

npm install @licensehub/license-client
# or
yarn add @licensehub/license-client
# or
pnpm add @licensehub/license-client

πŸ—οΈ Architecture Overview

The License Client operates on a client-server architecture:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    HTTPS/JWT    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Your App      β”‚ ◄──────────────► β”‚  License Server β”‚
β”‚                 β”‚                  β”‚                 β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚                  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚License      β”‚ β”‚                  β”‚ β”‚License      β”‚ β”‚
β”‚ β”‚Client       β”‚ β”‚                  β”‚ β”‚Management   β”‚ β”‚
β”‚ β”‚             β”‚ β”‚                  β”‚ β”‚API          β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚                  β”‚ β”‚             β”‚ β”‚
β”‚ β”‚ β”‚Cache    β”‚ β”‚ β”‚                  β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚(JWT)    β”‚ β”‚ β”‚                  β”‚ β”‚ β”‚Database β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚                  β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚                  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Components:

  1. License Client - This package, handles all client-side operations
  2. License Server - Backend service that manages licenses and activations
  3. JWT Tokens - Secure tokens for offline validation
  4. Cache Layer - Intelligent caching for performance and offline support

πŸ”§ Quick Start

Basic Setup

import { LicenseClient } from '@licensehub/license-client';

// Initialize the client with your license server
const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://your-license-server.com/api/v1'
});

// Validate a license
const response = await client.validate({
  licenseKey: 'YOUR-LICENSE-KEY',
  domain: 'your-app-domain.com'
});

if (response.success) {
  console.log('βœ… License is valid!');
  // Your protected code here
} else {
  console.log('❌ License validation failed:', response.messages);
  // Handle invalid license
}

Complete Configuration

import { LicenseClient, type TypeLicenseClientOptions } from '@licensehub/license-client';

const options: TypeLicenseClientOptions = {
  // Required: Your license server URL
  LICENSE_SERVER_URL: 'https://your-license-server.com/api/v1',
  
  // Optional: Validation scheduler settings
  validationScheduler: {
    minIntervalHours: 12, // Default: 24
    maxIntervalHours: 48, // Default: 72
    probabilisticFactor: 0.3 // Default: 0.1
  },

  // NOTE: The public key is now automatically retrieved and cached
  // from the server upon the first successful validation.
  // Providing it here is optional but can speed up the very first validation.
  publicKey: `-----BEGIN PUBLIC KEY-----...`,
  
  // Optional: Cache configuration (defaults to memory cache)
  cache: new MemoryCache({ prefix: 'license:' }),
  
  // For other cache types, import them first:
  // import '@cachehub/redis-cache';        // Enable Redis
  // import '@cachehub/upstash-redis-cache'; // Enable Upstash Redis
  // import '@cachehub/cloudflare-cache';    // Enable Cloudflare
  
  // Redis cache options (if using Redis)
  CACHE_REDIS_URL: 'redis://localhost:6379',
  CACHE_REDIS_TOKEN: 'your-redis-token', // for Upstash
  CACHE_KEY_PREFIX: 'license:',
  
  // Cloudflare cache options (if using Cloudflare)
  CACHE_BASE_URL: 'https://api.cloudflare.com/client/v4',
  CACHE_NAME: 'license-cache'
};

const client = new LicenseClient(options);

Response Structure:

{
  success: true,
  data: {
    token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", // JWT token
    publicKey: "-----BEGIN PUBLIC KEY-----...", // Public key for verification
    source: "cache" | "server" // Where the validation came from
  }
}

2. License Activation

Activates a license for a specific domain/device:

const response = await client.activate({
  licenseKey: 'LIC-XXXX-XXXX-XXXX',
  domain: 'myapp.com'
});

if (response.success) {
  console.log('License activated successfully!');
  // Store the JWT token for offline validation
  const token = response.data?.token;
} else {
  console.log('Activation failed:', response.messages);
}

3. License Deactivation

Deactivates a license and clears cached tokens:

const response = await client.deactivate({
  licenseKey: 'LIC-XXXX-XXXX-XXXX',
  domain: 'myapp.com'
});

if (response.success) {
  console.log('License deactivated successfully!');
}

🎯 Feature-Based Licensing

The License Client now supports feature-based licensing, allowing you to validate which features/products are enabled for a license without making server requests. This provides ultra-fast feature validation using cryptographically secure JWT tokens.

Quick Start

import { LicenseClient } from '@licensehub/license-client';

// Initialize client and activate license to get JWT token
const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://license.example.com/api/v1'
});

const response = await client.activate({
  licenseKey: 'LIC-XXXX-XXXX-XXXX',
  domain: 'myapp.com'
});

if (response.success) {
  console.log('License activated successfully!');
  console.log('Available features:', response.data?.features || []);
  
  // Check if specific features are available using validation
  const analyticsCheck = await client.validate({
    licenseKey: 'LIC-XXXX-XXXX-XXXX',
    domain: 'myapp.com'
  }, ['advanced-analytics']);
  
  if (analyticsCheck.success) {
    // Enable analytics features
    enableAnalyticsModule();
  }
}

Feature Validation

The License Client supports feature-based licensing through the validate() method's requiredFeatures parameter. License responses include feature arrays that can be used to control application functionality.

Basic Feature Validation

// Set default license payload
client.setLicensePayload({
  licenseKey: 'LIC-XXXX-XXXX-XXXX',
  domain: 'myapp.com'
});

// Check if specific feature is available
const response = await client.validate(undefined, ['advanced-analytics']);
if (response.success) {
  // Feature is available - enable analytics
  enableAnalyticsModule();
} else {
  // Feature not available or license invalid
  console.log('Analytics feature not available');
}

Multiple Feature Validation

// Check if multiple features are available (all required)
const integrationCheck = await client.validate(undefined, [
  'integrations:api', 
  'integrations:webhooks', 
  'integrations:sync'
]);

if (integrationCheck.success) {
  // All integration features available
  enableFullIntegrationSuite();
}

// Check for any of several features by validating individually
const hasBasicAnalytics = await client.validate(undefined, ['analytics:basic']);
const hasAdvancedAnalytics = await client.validate(undefined, ['analytics:advanced']);

if (hasBasicAnalytics.success || hasAdvancedAnalytics.success) {
  showAnalyticsMenu();
}

Getting Available Features

// Get all available features from license validation
const response = await client.validate();
if (response.success) {
  const features = response.data?.features || [];
  console.log('Available features:', features);
  
  // Use features array to enable UI components
  features.forEach(feature => {
    enableFeatureInUI(feature);
  });
}

Namespaced Feature Support

// Check for specific namespaced features
const analyticsResponse = await client.validate(undefined, ['analytics:advanced']);
const integrationsResponse = await client.validate(undefined, ['integrations:webhooks']);

// Server may support wildcard features like 'analytics:*'
// which would validate any analytics sub-feature
const customAnalyticsResponse = await client.validate(undefined, ['analytics:custom']);

Error Handling

Feature validation through the validate() method provides clear success/failure responses:

// Safe validation - never throws errors
const response = await client.validate(undefined, ['some-feature']);
if (response.success) {
  // Feature is available and license is valid
  enableFeature();
} else {
  // Feature not available, license invalid, or network error
  console.log('Feature validation failed:', response.messages);
}

// Safe to call with empty feature arrays (validates license only)
const licenseOnly = await client.validate(undefined, []);
// Returns success if license is valid, regardless of features

// Invalid feature names will result in validation failure
const invalidFeature = await client.validate(undefined, ['']);
// Returns { success: false } - empty feature names are not valid

Performance Characteristics

  • Cached validation: Subsequent feature checks use cached JWT tokens
  • Offline operation: Works without network when tokens are cached
  • Efficient caching: JWT tokens and validation responses cached intelligently
  • Cryptographically secure: JWT tokens cannot be tampered with client-side

Integration Examples

Express.js Middleware

import express from 'express';
import { LicenseClient } from '@licensehub/license-client';

const app = express();

// Feature validation middleware
const requireFeature = (featureName: string) => {
  return async (req: any, res: any, next: any) => {
    const licenseKey = req.headers.authorization?.replace('Bearer ', '');
    if (!licenseKey) {
      return res.status(401).json({ error: 'No license key provided' });
    }

    try {
      const client = new LicenseClient({ LICENSE_SERVER_URL: 'https://server.com/api/v1' });
      
      // Validate license with required feature
      const response = await client.validate({
        licenseKey,
        domain: req.hostname
      }, [featureName]);
      
      if (response.success) {
        next();
      } else {
        res.status(403).json({ 
          error: 'Feature not licensed',
          required: featureName,
          messages: response.messages
        });
      }
    } catch (error) {
      res.status(500).json({ 
        error: 'License validation failed',
        details: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  };
};

// Protected routes by feature
app.get('/api/analytics', requireFeature('analytics:basic'), (req, res) => {
  // Analytics endpoint only accessible with analytics feature
  res.json({ data: 'analytics data' });
});

app.get('/api/premium', requireFeature('premium:access'), (req, res) => {
  // Premium endpoint only accessible with premium feature
  res.json({ data: 'premium content' });
});

πŸ”’ Security & Robustness

The License Client is built with multiple layers of security to protect your software against tampering and license abuse.

1. Integrity-Protected Cache

All cached dataβ€”including JWT tokens and public keysβ€”is automatically protected by an integrity hash.

  • How it works: Before any data is cached, a unique hash is generated from the data and a secret salt derived from the license key. When data is retrieved, the hash is re-calculated and verified.
  • Why it's secure: This mechanism ensures that cached license information cannot be tampered with or modified on the client-side without being detected. If an integrity mismatch occurs, the cache is automatically invalidated, forcing a fresh validation with the server.

2. Periodic Server Validation

To prevent a user from using a valid (but now-revoked) license indefinitely, the client implements a periodic validation scheduler.

  • How it works: The client forces a re-validation with the server at unpredictable intervals, even if a valid, unexpired JWT exists in the cache. This is controlled by a combination of time-based and probabilistic checks.
  • Why it's robust: This proactive approach ensures that the license status is regularly synchronized with the server, catching any changes (like revocations or suspensions) in a timely manner.

3. JWT-Based Offline Validation

The core of the offline validation capability relies on JSON Web Tokens (JWT).

  1. Server Validation & Token Bundling: Upon a successful activation or validation, the server returns a signed JWT and the corresponding public key.
  2. Secure Caching: The JWT and public key are stored in the integrity-protected cache.
  3. Offline Cryptographic Verification: For subsequent validations, the client verifies the cached JWT's signature using the cached public key, requiring no network connection.
  4. Automatic Refresh: Expired tokens or failed integrity checks trigger a new server validation.
// The security flow
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    1. Validate     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Client    β”‚ ──────────────────► β”‚   Server    β”‚
β”‚             β”‚                     β”‚             β”‚
β”‚             β”‚ ◄────────────────── β”‚             β”‚
β”‚             β”‚    2. JWT & Key     β”‚             β”‚
β”‚             β”‚                     β”‚             β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚                     β”‚             β”‚
β”‚ β”‚ Cache   β”‚ β”‚ 3. Store Securely   β”‚             β”‚
β”‚ β”‚ JWT/Key β”‚ β”‚                     β”‚             β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚                     β”‚             β”‚
β”‚             β”‚                     β”‚             β”‚
β”‚             β”‚ 4. Offline          β”‚             β”‚
β”‚             β”‚    Validation       β”‚             β”‚
β”‚             β”‚    (No Network)     β”‚             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Public Key Management

The client handles public key retrieval and caching automatically to simplify setup.

  • Automatic Retrieval: On the first successful activation or validation, the server bundles the public key with the JWT token.
  • Secure Caching: The client then securely stores this public key in the integrity-protected cache for all future offline JWT verifications.
  • Manual Pre-loading (Optional): You can optionally provide the publicKey during client initialization. This can speed up the very first validation by avoiding the initial fetch, but is not required for normal operation.

πŸ’Ύ Advanced Caching System

The License Client includes a sophisticated caching system powered by CacheHub that provides flexible storage options for JWT tokens and public keys.

πŸ“¦ Pre-bundled Cache

The package comes pre-bundled with @cachehub/memory-cache which provides:

  • βœ… Zero Configuration - Works out of the box
  • βœ… Lightweight - Minimal memory footprint
  • βœ… Fast Access - In-memory storage for maximum performance
  • βœ… No Dependencies - No external services required
// Memory cache is enabled by default - no configuration needed
const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://server.com/api/v1'
  // CACHE_TYPE: 'memory-cache' is implicit
});

πŸš€ Available Cache Providers

The License Client supports multiple cache providers through CacheHub. All cache packages are included as dependencies but need to be explicitly imported to be available:

Cache Provider Package Bundle Size Use Case
Memory Cache @cachehub/memory-cache ~2KB βœ… Pre-bundled - Single instance apps
Redis Cache @cachehub/redis-cache ~743KB Traditional Redis servers
Upstash Redis @cachehub/upstash-redis-cache ~138KB Serverless Redis (Vercel, Netlify)
Cloudflare Cache @cachehub/cloudflare-cache ~3KB Cloudflare Workers/Edge

πŸ”§ Enabling Additional Cache Providers

To use cache providers beyond the default memory cache, you need to import them in your application:

Method 1: Import in Your Main Application File

// app.ts or index.ts
import '@cachehub/redis-cache';        // Enable Redis cache
import '@cachehub/upstash-redis-cache'; // Enable Upstash Redis cache
import '@cachehub/cloudflare-cache';    // Enable Cloudflare cache

import { LicenseClient } from '@licensehub/license-client';

// Now you can use any cache type
const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://server.com/api/v1',
  CACHE_TYPE: 'redis-cache',
  CACHE_REDIS_URL: 'redis://localhost:6379'
});

Method 2: Import Before Creating Client

// Import only the cache you need
import '@cachehub/upstash-redis-cache';
import { LicenseClient } from '@licensehub/license-client';

const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://server.com/api/v1',
  CACHE_TYPE: 'upstash-redis-cache',
  CACHE_REDIS_URL: 'https://your-redis.upstash.io',
  CACHE_REDIS_TOKEN: 'your-token'
});

πŸ“‹ Cache Configuration Examples

1. Memory Cache (Default - Pre-bundled)

const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://server.com/api/v1'
  // No additional configuration needed
});

2. Redis Cache

// Import the Redis cache provider
import '@cachehub/redis-cache';
import { LicenseClient } from '@licensehub/license-client';

const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://server.com/api/v1',
  CACHE_TYPE: 'redis-cache',
  CACHE_REDIS_URL: 'redis://localhost:6379',
  // Optional Redis configuration
  CACHE_KEY_PREFIX: 'license:' // Prefix for all cache keys
});

3. Upstash Redis Cache (Serverless)

// Import the Upstash Redis cache provider
import '@cachehub/upstash-redis-cache';
import { LicenseClient } from '@licensehub/license-client';

const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://server.com/api/v1',
  CACHE_TYPE: 'upstash-redis-cache',
  CACHE_REDIS_URL: 'https://your-redis.upstash.io',
  CACHE_REDIS_TOKEN: 'your-upstash-token',
  CACHE_KEY_PREFIX: 'license:' // Optional prefix
});

4. Cloudflare Cache (Edge Computing)

// Import the Cloudflare cache provider
import '@cachehub/cloudflare-cache';
import { LicenseClient } from '@licensehub/license-client';

const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://server.com/api/v1',
  CACHE_TYPE: 'cloudflare-cache',
  CACHE_BASE_URL: 'https://api.cloudflare.com/client/v4',
  CACHE_NAME: 'license-cache' // Optional cache name
});

🌍 Environment-Based Cache Configuration

// .env file
CACHE_TYPE=upstash-redis-cache
CACHE_REDIS_URL=https://your-redis.upstash.io
CACHE_REDIS_TOKEN=your-token
CACHE_KEY_PREFIX=license:

// app.ts
import '@cachehub/upstash-redis-cache'; // Import based on your CACHE_TYPE
import { LicenseClient } from '@licensehub/license-client';

const client = new LicenseClient({
  LICENSE_SERVER_URL: process.env.LICENSE_SERVER_URL!,
  CACHE_TYPE: process.env.CACHE_TYPE,
  CACHE_REDIS_URL: process.env.CACHE_REDIS_URL,
  CACHE_REDIS_TOKEN: process.env.CACHE_REDIS_TOKEN,
  CACHE_KEY_PREFIX: process.env.CACHE_KEY_PREFIX
});

πŸ”„ Dynamic Cache Provider Loading

For applications that need to switch cache providers dynamically:

// cache-loader.ts
export function loadCacheProvider(cacheType: string) {
  switch (cacheType) {
    case 'redis-cache':
      return import('@cachehub/redis-cache');
    case 'upstash-redis-cache':
      return import('@cachehub/upstash-redis-cache');
    case 'cloudflare-cache':
      return import('@cachehub/cloudflare-cache');
    default:
      // Memory cache is pre-bundled, no import needed
      return Promise.resolve();
  }
}

// usage
async function createLicenseClient(cacheType: string) {
  await loadCacheProvider(cacheType);
  
  return new LicenseClient({
    LICENSE_SERVER_URL: 'https://server.com/api/v1',
    CACHE_TYPE: cacheType,
    // ... other config
  });
}

🎯 Cache Behavior & Features

  • JWT Tokens: Cached with automatic TTL based on token expiration
  • Public Keys: Cached for 1 hour to reduce server requests
  • Operation Results: Cached to prevent duplicate requests
  • Automatic Cleanup: Expired tokens are automatically removed
  • Graceful Fallback: If cache fails, operations continue without caching
  • Key Prefixing: Optional prefixes to avoid key collisions
  • TTL Support: Automatic expiration based on JWT token lifetime

πŸ“Š Cache Performance Comparison

Cache Type Read Speed Write Speed Persistence Scalability Use Case
Memory ~1ms ~1ms ❌ Process only ❌ Single instance Development, single-server apps
Redis ~5-10ms ~5-10ms βœ… Persistent βœ… Multi-instance Production, distributed apps
Upstash ~10-20ms ~10-20ms βœ… Persistent βœ… Serverless Vercel, Netlify, edge functions
Cloudflare ~5-15ms ~5-15ms βœ… Global CDN βœ… Edge computing Cloudflare Workers

πŸ› οΈ Cache Troubleshooting

Common Issues and Solutions

  1. Cache provider not found

    // Error: Cache factory for 'redis-cache' not found
    // Solution: Import the cache provider
    import '@cachehub/redis-cache';
  2. Connection failures

    // The client will automatically fall back to no caching
    // Check console for cache creation warnings
    console.warn('Failed to create cache of type redis-cache: Connection failed');
  3. Bundle size concerns

    // Only import the cache providers you actually use
    // Memory cache is always available and lightweight
    const client = new LicenseClient({
      LICENSE_SERVER_URL: 'https://server.com/api/v1'
      // Uses memory cache by default - no imports needed
    });

πŸ”„ Singleton Pattern

For applications that need a single, shared license client instance:

import { LicenseClient, LicenseClientSingleton } from '@licensehub/license-client';

// Initialize and set the singleton
const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://server.com/api/v1'
});

LicenseClientSingleton.setInstance(client);

// Use the singleton anywhere in your app
const sharedClient = LicenseClientSingleton.getInstance();
if (sharedClient) {
  const response = await sharedClient.validate({
    licenseKey: 'key'
  });
}

Benefits:

  • Consistent license state across your application
  • Shared cache and configuration
  • Prevents multiple instances from conflicting
  • Simplifies license management in complex applications

πŸ“Š Response Types and Error Handling

Success Response

interface TypeResponse {
  success: true;
  data?: {
    token?: string;           // JWT token for offline validation
    source?: 'cache' | 'server'; // Where the response came from
    [key: string]: unknown;  // Additional data
  };
  messages?: TypeMessage[]; // Optional success messages
}

Error Response

interface TypeErrorResponse {
  success: false;
  messages: TypeMessage[]; // Required error messages
}

interface TypeMessage {
  code: string;    // Error code (e.g., 'LICENSE_EXPIRED')
  text: string;    // Human-readable message
}

Common Error Codes

Code Description
LICENSE_NOT_FOUND License key doesn't exist
LICENSE_EXPIRED License has expired
LICENSE_INACTIVE License is deactivated
LICENSE_REVOKED License has been revoked
MAX_ACTIVATIONS_REACHED Too many activations
INVALID_ACTIVATION Invalid activation conditions
REQUEST_FAILED Network or server error
MISSING_LICENSE_DATA Required license data missing

Error Handling Examples

const response = await client.validate({ licenseKey: 'key' });

if (!response.success) {
  response.messages.forEach(message => {
    switch (message.code) {
      case 'LICENSE_EXPIRED':
        console.log('License has expired. Please renew.');
        break;
      case 'MAX_ACTIVATIONS_REACHED':
        console.log('Too many devices. Please deactivate unused devices.');
        break;
      case 'REQUEST_FAILED':
        console.log('Network error. Trying offline validation...');
        break;
      default:
        console.log(`Error: ${message.text}`);
    }
  });
}

🌐 Offline Support

The License Client provides robust offline capabilities:

How It Works

  1. Initial Online Validation: First validation requires internet connection
  2. JWT Caching: Valid JWT tokens are cached locally
  3. Offline Validation: Subsequent validations work without internet
  4. Automatic Refresh: Expired tokens trigger online validation when possible

Example Usage

// This works offline if a valid JWT is cached
const response = await client.validate({
  licenseKey: 'cached-license-key',
  domain: 'myapp.com'
});

if (response.success) {
  console.log(`Validated ${response.data?.source}`); // "cache" or "server"
}

Offline Validation Flow

async function validateLicense(licenseKey: string) {
  const response = await client.validate({ licenseKey, domain: 'myapp.com' });
  
  if (response.success) {
    if (response.data?.source === 'cache') {
      console.log('βœ… Offline validation successful');
    } else {
      console.log('βœ… Online validation successful');
    }
    return true;
  } else {
    console.log('❌ Validation failed:', response.messages);
    return false;
  }
}

πŸ› οΈ Advanced Usage Examples

Environment-Based Configuration

// .env file
LICENSE_SERVER_URL=https://license-server.com/api/v1
LICENSE_PUBLIC_KEY=-----BEGIN PUBLIC KEY-----...
CACHE_TYPE=redis-cache
CACHE_REDIS_URL=redis://localhost:6379

// Application code
import { LicenseClient } from '@licensehub/license-client';

const client = new LicenseClient({
  LICENSE_SERVER_URL: process.env.LICENSE_SERVER_URL!,
  LICENSE_PUBLIC_KEY: process.env.LICENSE_PUBLIC_KEY,
  CACHE_TYPE: process.env.CACHE_TYPE,
  CACHE_REDIS_URL: process.env.CACHE_REDIS_URL
});

License Validation Middleware

Express.js Middleware

import { Request, Response, NextFunction } from 'express';
import { LicenseClient } from '@licensehub/license-client';

const licenseClient = new LicenseClient({
  LICENSE_SERVER_URL: process.env.LICENSE_SERVER_URL!
});

export async function validateLicense(req: Request, res: Response, next: NextFunction) {
  const licenseKey = req.headers['x-license-key'] as string;
  const domain = req.headers['host'];

  if (!licenseKey) {
    return res.status(401).json({ error: 'License key required' });
  }

  const response = await licenseClient.validate({
    licenseKey,
    domain
  });

  if (response.success) {
    // Add license info to request
    req.license = response.data;
    next();
  } else {
    res.status(403).json({ 
      error: 'Invalid license',
      messages: response.messages 
    });
  }
}

// Usage
app.use('/api/protected', validateLicense);

Hono Middleware

import { Context, Next } from 'hono';
import { LicenseClient } from '@licensehub/license-client';

const licenseClient = new LicenseClient({
  LICENSE_SERVER_URL: process.env.LICENSE_SERVER_URL!
});

export async function validateLicense(c: Context, next: Next) {
  const licenseKey = c.req.header('x-license-key');
  const domain = c.req.header('host');

  if (!licenseKey) {
    return c.json({ error: 'License key required' }, 401);
  }

  const response = await licenseClient.validate({
    licenseKey,
    domain
  });

  if (response.success) {
    // Add license info to context
    c.set('license', response.data);
    await next();
  } else {
    return c.json({ 
      error: 'Invalid license',
      messages: response.messages 
    }, 403);
  }
}

// Usage
import { Hono } from 'hono';

const app = new Hono();

// Apply to specific routes
app.use('/api/protected/*', validateLicense);

// Or apply to all routes
app.use('*', validateLicense);

// Access license data in handlers
app.get('/api/protected/data', (c) => {
  const license = c.get('license');
  return c.json({ 
    message: 'Protected data',
    licenseSource: license?.source 
  });
});

Astro Middleware

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { LicenseClient } from '@licensehub/license-client';

const licenseClient = new LicenseClient({
  LICENSE_SERVER_URL: import.meta.env.LICENSE_SERVER_URL
});

export const onRequest = defineMiddleware(async (context, next) => {
  // Only validate license for API routes
  if (context.url.pathname.startsWith('/api/protected/')) {
    const licenseKey = context.request.headers.get('x-license-key');
    const domain = context.request.headers.get('host');

    if (!licenseKey) {
      return new Response(
        JSON.stringify({ error: 'License key required' }), 
        { 
          status: 401,
          headers: { 'Content-Type': 'application/json' }
        }
      );
    }

    const response = await licenseClient.validate({
      licenseKey,
      domain
    });

    if (!response.success) {
      return new Response(
        JSON.stringify({ 
          error: 'Invalid license',
          messages: response.messages 
        }), 
        { 
          status: 403,
          headers: { 'Content-Type': 'application/json' }
        }
      );
    }

    // Add license info to locals for use in API routes
    context.locals.license = response.data;
  }

  return next();
});
// src/pages/api/protected/data.ts
import type { APIRoute } from 'astro';

export const GET: APIRoute = async ({ locals }) => {
  // Access license data from middleware
  const license = locals.license;
  
  return new Response(
    JSON.stringify({ 
      message: 'Protected data',
      licenseSource: license?.source 
    }),
    {
      status: 200,
      headers: { 'Content-Type': 'application/json' }
    }
  );
};

Next.js Middleware

// middleware.ts (in project root)
import { NextRequest, NextResponse } from 'next/server';
import { LicenseClient } from '@licensehub/license-client';

const licenseClient = new LicenseClient({
  LICENSE_SERVER_URL: process.env.LICENSE_SERVER_URL!
});

export async function middleware(request: NextRequest) {
  // Only validate license for API routes
  if (request.nextUrl.pathname.startsWith('/api/protected/')) {
    const licenseKey = request.headers.get('x-license-key');
    const domain = request.headers.get('host');

    if (!licenseKey) {
      return NextResponse.json(
        { error: 'License key required' },
        { status: 401 }
      );
    }

    const response = await licenseClient.validate({
      licenseKey,
      domain
    });

    if (!response.success) {
      return NextResponse.json(
        { 
          error: 'Invalid license',
          messages: response.messages 
        },
        { status: 403 }
      );
    }

    // Add license info to request headers for API routes
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-license-data', JSON.stringify(response.data));

    return NextResponse.next({
      request: {
        headers: requestHeaders,
      },
    });
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/api/protected/:path*'
};
// pages/api/protected/data.ts or app/api/protected/data/route.ts
import { NextRequest } from 'next/server';

export async function GET(request: NextRequest) {
  // Access license data from middleware
  const licenseDataHeader = request.headers.get('x-license-data');
  const license = licenseDataHeader ? JSON.parse(licenseDataHeader) : null;
  
  return Response.json({ 
    message: 'Protected data',
    licenseSource: license?.source 
  });
}

Framework Compatibility

Framework Runtime Recommended Cache Notes
Express.js Node.js Redis Cache Traditional server deployment
Hono Cloudflare Workers Cloudflare Cache Edge runtime optimized
Hono Node.js/Bun Memory/Redis Cache Server deployment
Astro SSR/SSG Upstash Redis Serverless friendly
Next.js Node.js Redis Cache Traditional deployment
Next.js Edge Runtime Memory/Upstash Edge runtime compatible
Fastify Node.js Redis Cache High performance server
SvelteKit Node.js/Serverless Memory/Upstash Adapter dependent

Framework-Specific Considerations

Hono (Edge Runtime)

// For Cloudflare Workers, enable Cloudflare cache
import '@cachehub/cloudflare-cache';
import { LicenseClient } from '@licensehub/license-client';

const licenseClient = new LicenseClient({
  LICENSE_SERVER_URL: env.LICENSE_SERVER_URL,
  CACHE_TYPE: 'cloudflare-cache',
  CACHE_BASE_URL: 'https://api.cloudflare.com/client/v4',
  CACHE_NAME: 'license-cache'
});

Astro (SSR/SSG)

// For serverless deployments, use Upstash Redis
import '@cachehub/upstash-redis-cache';

const licenseClient = new LicenseClient({
  LICENSE_SERVER_URL: import.meta.env.LICENSE_SERVER_URL,
  CACHE_TYPE: 'upstash-redis-cache',
  CACHE_REDIS_URL: import.meta.env.UPSTASH_REDIS_URL,
  CACHE_REDIS_TOKEN: import.meta.env.UPSTASH_REDIS_TOKEN
});

Next.js (Edge Runtime)

// For Edge Runtime, use memory cache or Upstash
export const runtime = 'edge';

const licenseClient = new LicenseClient({
  LICENSE_SERVER_URL: process.env.LICENSE_SERVER_URL!,
  // Memory cache works well for Edge Runtime
  CACHE_TYPE: 'memory-cache'
});

Periodic License Validation

class LicenseManager {
  private client: LicenseClient;
  private validationInterval?: NodeJS.Timeout;

  constructor() {
    this.client = new LicenseClient({
      LICENSE_SERVER_URL: process.env.LICENSE_SERVER_URL!
    });
  }

  async startPeriodicValidation(licenseKey: string, domain: string) {
    // Validate immediately
    const isValid = await this.validateLicense(licenseKey, domain);
    if (!isValid) {
      throw new Error('Initial license validation failed');
    }

    // Set up periodic validation (every hour)
    this.validationInterval = setInterval(async () => {
      const valid = await this.validateLicense(licenseKey, domain);
      if (!valid) {
        console.log('License validation failed - shutting down');
        process.exit(1);
      }
    }, 60 * 60 * 1000); // 1 hour
  }

  private async validateLicense(licenseKey: string, domain: string): Promise<boolean> {
    const response = await this.client.validate({ licenseKey, domain });
    return response.success;
  }

  stopPeriodicValidation() {
    if (this.validationInterval) {
      clearInterval(this.validationInterval);
    }
  }
}

Multi-License Management

class MultiLicenseManager {
  private clients: Map<string, LicenseClient> = new Map();

  addLicenseServer(name: string, serverUrl: string) {
    const client = new LicenseClient({
      LICENSE_SERVER_URL: serverUrl
    });
    this.clients.set(name, client);
  }

  async validateAnyLicense(licenseKey: string, domain: string): Promise<boolean> {
    for (const [name, client] of this.clients) {
      try {
        const response = await client.validate({ licenseKey, domain });
        if (response.success) {
          console.log(`License validated on server: ${name}`);
          return true;
        }
      } catch (error) {
        console.log(`Failed to validate on ${name}:`, error);
      }
    }
    return false;
  }
}

// Usage
const manager = new MultiLicenseManager();
manager.addLicenseServer('primary', 'https://primary-license-server.com/api/v1');
manager.addLicenseServer('backup', 'https://backup-license-server.com/api/v1');

const isValid = await manager.validateAnyLicense('license-key', 'myapp.com');

Client-Side Usage (Browser)

// For browser environments, memory cache is recommended
import { LicenseClient } from '@licensehub/license-client';

const client = new LicenseClient({
  LICENSE_SERVER_URL: 'https://your-license-server.com/api/v1',
  // Memory cache works well in browsers
  CACHE_TYPE: 'memory-cache'
});

// Validate license in browser
async function validateClientLicense() {
  try {
    const response = await client.validate({
      licenseKey: 'user-license-key',
      domain: window.location.hostname
    });

    if (response.success) {
      console.log('βœ… License valid, enabling features');
      enablePremiumFeatures();
    } else {
      console.log('❌ License invalid, showing upgrade prompt');
      showUpgradePrompt();
    }
  } catch (error) {
    console.error('License validation failed:', error);
    // Handle offline scenario or network errors
    handleOfflineMode();
  }
}

function enablePremiumFeatures() {
  // Enable premium UI components
  document.querySelectorAll('.premium-feature').forEach(el => {
    el.style.display = 'block';
  });
}

function showUpgradePrompt() {
  // Show upgrade modal or redirect to pricing page
  window.location.href = '/upgrade';
}

function handleOfflineMode() {
  // Try to use cached license data or show limited functionality
  console.log('Working in offline mode with cached license');
}

⚠️ Security Note: Client-side license validation should be used for UX enhancement only. Always validate licenses server-side for security-critical operations, as client-side code can be modified by users.

πŸ” Debugging and Troubleshooting

Enable Debug Logging

// The client logs important events to console
// Check browser console or Node.js output for:
// - Public key loading status
// - Cache creation success/failure
// - JWT verification errors
// - Network request failures

Common Issues and Solutions

  1. "Public key could not be loaded"

    // Solution: Provide public key explicitly
    const client = new LicenseClient({
      LICENSE_SERVER_URL: 'https://server.com/api/v1',
      LICENSE_PUBLIC_KEY: '-----BEGIN PUBLIC KEY-----...'
    });
  2. "Failed to create cache"

    // Solution: Check cache configuration
    const client = new LicenseClient({
      LICENSE_SERVER_URL: 'https://server.com/api/v1',
      CACHE_TYPE: 'memory-cache', // Use simple memory cache
    });
  3. Network timeout issues

    // The client will fall back to cached validation
    // Ensure you have a valid cached JWT for offline scenarios

Testing License Validation

import { LicenseClient } from '@licensehub/license-client';

async function testLicenseValidation() {
  const client = new LicenseClient({
    LICENSE_SERVER_URL: 'https://your-server.com/api/v1'
  });

  console.log('Testing license validation...');
  
  const response = await client.validate({
    licenseKey: 'TEST-LICENSE-KEY',
    domain: 'test-domain.com'
  });

  console.log('Response:', JSON.stringify(response, null, 2));
  
  if (response.success) {
    console.log('βœ… License is valid');
    console.log('Token source:', response.data?.source);
  } else {
    console.log('❌ License validation failed');
    response.messages?.forEach(msg => {
      console.log(`Error ${msg.code}: ${msg.text}`);
    });
  }
}

testLicenseValidation().catch(console.error);

πŸ“‹ TypeScript Types

The package exports comprehensive TypeScript types for full type safety:

import type {
  // Main types
  TypeLicenseClientOptions,
  TypeLicensePayload,
  TypeResponse,
  TypeErrorResponse,
  TypeMessage,
  TypeDefaultResponseData,
  
  // Enums
  EnumMessageCode,
  EnumMessageText,
  EnumLicenseStatus,
  EnumLicenseActivationStatus
} from '@licensehub/license-client';

πŸš€ Performance Optimization

Best Practices

  1. Use Singleton Pattern for shared instances
  2. Set Default Payload to avoid repetitive parameter passing
  3. Enable Caching for better performance (memory cache is pre-bundled)
  4. Import Cache Providers before using Redis, Upstash, or Cloudflare caching
  5. Use Redis Cache for distributed applications
  6. Use Upstash Redis for serverless environments
  7. Implement Periodic Validation instead of validating on every request

Performance Metrics

  • Memory Cache (Pre-bundled): ~1ms validation time for cached tokens
  • Redis Cache: ~5-10ms validation time for cached tokens
  • Upstash Redis Cache: ~10-20ms validation time for cached tokens
  • Cloudflare Cache: ~5-15ms validation time for cached tokens
  • Server Validation: ~100-500ms depending on network latency
  • JWT Verification: ~1-2ms for cryptographic verification

🀝 Support and Resources

  • Documentation: This README and inline code documentation
  • Website: 1teamsoftware.com
  • License Server: Contact us for license server setup and configuration

πŸ“„ License

This package is proprietary software owned by 1TeamSoftware. All rights reserved. Usage is subject to the terms of our Proprietary Software License Agreement which prohibits copying, modification, distribution, or creation of derivative works. See LICENSE for the complete terms and conditions.


Ready to protect your software? Start with the quick start guide above and explore the advanced features as your needs grow. The License Client is designed to scale from simple license validation to complex multi-server, multi-license scenarios.