@latentsearch/license-client
v0.2.0
Published
TypeScript client for Táillí License Manager - offline seat-based licensing
Maintainers
Readme
Táillí License Client for TypeScript
A friendly TypeScript/JavaScript client for the Táillí License Manager. Manage seats and API keys for offline/air-gapped deployments with minimal code.
Installation
npm install @latentsearch/license-clientOr with yarn:
yarn add @latentsearch/license-clientQuick Start
import { LicenseClient } from '@latentsearch/license-client';
// Connect to your License Manager
const client = new LicenseClient('http://localhost:8787', {
productId: 'my-product'
});
// Use a seat with automatic cleanup
await client.withSeat(async (seat) => {
console.log(`Allocated seat: ${seat.seatId}`);
// Do your licensed work here
await runMyApplication();
});
// Seat automatically released when doneFeatures
- Simple callback API - Seats are automatically released, even on exceptions
- Auto-refresh - Tokens are refreshed in the background
- Full TypeScript support - Complete type definitions included
- Zero dependencies - Uses only the Fetch API
- Works everywhere - Node.js, browsers, Deno, Bun, edge runtimes
Usage Examples
Check License Status
import { LicenseClient } from '@latentsearch/license-client';
const client = new LicenseClient('http://localhost:8787', {
productId: 'video-processor'
});
const status = await client.status();
console.log(`Product: ${status.productId}`);
console.log(`Seats: ${status.seatsInUse}/${status.seats} in use`);
console.log(`Expires: ${status.expiry}`);
console.log(`Features: ${status.features.join(', ')}`);Use a Seat (Recommended)
The withSeat method is the cleanest way to use seats:
import { LicenseClient, SeatLimitError, LicenseExpiredError } from '@latentsearch/license-client';
const client = new LicenseClient('http://localhost:8787', {
productId: 'video-processor'
});
try {
const result = await client.withSeat(async (seat) => {
console.log(`Got seat ${seat.seatId}`);
// Your licensed code here
return await processVideos();
});
console.log('Processing complete:', result);
} catch (e) {
if (e instanceof SeatLimitError) {
console.log('All seats are in use. Please try again later.');
} else if (e instanceof LicenseExpiredError) {
console.log('Your license has expired. Please contact support.');
} else {
throw e;
}
}Add Context for Auditing
Track who's using seats for compliance and debugging:
await client.withSeat(
async (seat) => {
await runAnalysis();
},
{
context: {
user: 'alice',
workstation: 'lab-pc-42'
}
}
);Manual Seat Management
For more control, manage seats directly:
// Allocate a seat
const seat = await client.allocate({
context: { user: 'bob' }
});
try {
// Do work...
for (const batch of dataBatches) {
await process(batch);
}
} finally {
// Always release when done
await seat.release();
}Disable Auto-Refresh
For short operations, you might not need background refresh:
await client.withSeat(
async (seat) => {
await quickOperation(); // Done in < 1 minute
},
{ autoRefresh: false }
);Check Feature Entitlements
See what features your license enables:
const entitlements = await client.getEntitlements();
if (entitlements.features.includes('ai-enhancement')) {
enableAiFeatures();
}
if (entitlements.features.includes('batch-processing')) {
enableBatchMode();
}
console.log(`API keys: ${entitlements.keysIssued}/${entitlements.maxKeys} used`);Verify User API Keys
Validate JWTs issued by the License Manager:
try {
const payload = await client.verifyKey(userProvidedToken);
console.log(`Valid token for product: ${payload.productId}`);
console.log(`Features: ${payload.features}`);
} catch (e) {
console.log('Invalid or expired token');
}Error Handling
The client provides specific error classes for common scenarios:
import {
LicenseClient,
LicenseError, // Base class for all errors
SeatLimitError, // All seats are in use
LicenseExpiredError, // License has expired
ConnectionError, // Can't reach License Manager
} from '@latentsearch/license-client';
const client = new LicenseClient('http://localhost:8787', {
productId: 'my-product'
});
try {
await client.withSeat(async (seat) => {
await doWork();
});
} catch (e) {
if (e instanceof SeatLimitError) {
// All seats taken - maybe queue the request
await queueForLater();
} else if (e instanceof LicenseExpiredError) {
// License expired - notify admin
await notifyAdmin('License expired!');
} else if (e instanceof ConnectionError) {
// Can't reach LM - maybe it's down
await useCachedStateOrFail();
} else if (e instanceof LicenseError) {
// Other license-related errors
console.error(`License error: ${e.message}`);
} else {
throw e;
}
}Configuration
Client Options
const client = new LicenseClient('http://localhost:8787', {
productId: 'my-product', // Your product ID (required)
timeout: 30000, // Request timeout in ms (default: 30000)
});Allocation Options
await client.withSeat(callback, {
context: { user: 'alice' }, // Metadata for auditing
autoRefresh: true, // Keep token fresh (default: true)
refreshInterval: 30000, // Refresh interval in ms (default: 30000)
});API Reference
LicenseClient
| Method | Description |
|--------|-------------|
| status() | Get license status (seats, expiry, features) |
| allocate(options?) | Allocate a seat, returns Seat object |
| withSeat(callback, options?) | Use a seat with automatic cleanup |
| verifyKey(token) | Verify a user API key (JWT) |
| getEntitlements() | Get feature entitlements |
Seat
| Property/Method | Description |
|-----------------|-------------|
| seatId | Unique identifier for this seat |
| token | Current authentication token |
| expiresAt | Token expiration time (ISO 8601) |
| release() | Release the seat back to the pool |
| refresh() | Manually refresh the token |
Types
interface LicenseStatus {
productId: string;
seats: number;
seatsInUse: number;
seatsAvailable: number;
expiry: string;
features: string[];
valid: boolean;
}
interface Entitlements {
features: string[];
scopes: string[];
maxKeys: number;
keysIssued: number;
keysAvailable: number;
expiry: string;
}
interface SeatContext {
user?: string;
workstation?: string;
[key: string]: unknown;
}Browser Usage
The client works in browsers out of the box:
<script type="module">
import { LicenseClient } from 'https://unpkg.com/@latentsearch/license-client';
const client = new LicenseClient('http://localhost:8787', {
productId: 'my-product'
});
const status = await client.status();
console.log(status);
</script>Node.js Usage
Works with Node.js 18+ (which has built-in fetch):
import { LicenseClient } from '@latentsearch/license-client';
const client = new LicenseClient('http://localhost:8787', {
productId: 'my-product'
});
await client.withSeat(async (seat) => {
// Your code here
});For Node.js < 18, you'll need a fetch polyfill like node-fetch.
License
MIT License - see the main Táillí repository for details.
