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:
- License Client - This package, handles all client-side operations
- License Server - Backend service that manages licenses and activations
- JWT Tokens - Secure tokens for offline validation
- 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 validPerformance 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).
- Server Validation & Token Bundling: Upon a successful activation or validation, the server returns a signed JWT and the corresponding public key.
- Secure Caching: The JWT and public key are stored in the integrity-protected cache.
- Offline Cryptographic Verification: For subsequent validations, the client verifies the cached JWT's signature using the cached public key, requiring no network connection.
- 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
publicKeyduring 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
Cache provider not found
// Error: Cache factory for 'redis-cache' not found // Solution: Import the cache provider import '@cachehub/redis-cache';
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');
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
- Initial Online Validation: First validation requires internet connection
- JWT Caching: Valid JWT tokens are cached locally
- Offline Validation: Subsequent validations work without internet
- 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 failuresCommon Issues and Solutions
"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-----...' });
"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 });
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
- Use Singleton Pattern for shared instances
- Set Default Payload to avoid repetitive parameter passing
- Enable Caching for better performance (memory cache is pre-bundled)
- Import Cache Providers before using Redis, Upstash, or Cloudflare caching
- Use Redis Cache for distributed applications
- Use Upstash Redis for serverless environments
- 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.