Implement IronLicensing JavaScript SDK

- LicenseClient for validation and activation
- Feature checking with hasFeature/requireFeature
- Trial management
- Purchase/checkout flow
- Offline caching with grace period
- License change events
- Browser/Node.js compatible storage
- Full TypeScript support
- Comprehensive README
This commit is contained in:
David Friedel 2025-12-25 11:07:00 +00:00
parent 38ed535f32
commit ed66914a92
9 changed files with 1379 additions and 2 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
node_modules/
dist/
*.log
.DS_Store
.env
.env.local
*.tgz
.ironlicensing/

326
README.md
View File

@ -1,2 +1,324 @@
# ironlicensing-js # IronLicensing SDK for JavaScript/TypeScript
IronLicensing SDK for JavaScript/TypeScript - Software licensing and activation
Software licensing and activation SDK for JavaScript/TypeScript applications. Validate licenses, manage activations, check features, and handle trials.
[![npm](https://img.shields.io/npm/v/@ironservices/licensing.svg)](https://www.npmjs.com/package/@ironservices/licensing)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
## Installation
```bash
npm install @ironservices/licensing
```
## Quick Start
### Validate a License
```typescript
import { LicenseClient } from '@ironservices/licensing';
const client = new LicenseClient({
publicKey: 'pk_live_xxxxx',
productSlug: 'my-app',
});
// Validate a license key
const result = await client.validate('IRON-XXXX-XXXX-XXXX-XXXX');
if (result.valid) {
console.log('License is valid!');
console.log('Expires:', result.license?.expiresAt);
}
```
### Activate a License
```typescript
import { LicenseClient } from '@ironservices/licensing';
const client = new LicenseClient({
publicKey: 'pk_live_xxxxx',
productSlug: 'my-app',
});
// Activate on this machine
const result = await client.activate('IRON-XXXX-XXXX-XXXX-XXXX');
if (result.valid) {
console.log('License activated!');
console.log('Activations:', result.activations?.length);
}
```
### Check Features
```typescript
import { LicenseClient, LicenseRequiredError } from '@ironservices/licensing';
const client = new LicenseClient({
publicKey: 'pk_live_xxxxx',
productSlug: 'my-app',
});
await client.validate('IRON-XXXX-XXXX-XXXX-XXXX');
// Check if feature is enabled
if (client.hasFeature('premium')) {
// Enable premium features
}
// Or require a feature (throws if not available)
try {
client.requireFeature('enterprise');
// Enterprise code here
} catch (e) {
if (e instanceof LicenseRequiredError) {
console.log('Enterprise license required');
}
}
```
### Using the Global Client
```typescript
import * as licensing from '@ironservices/licensing';
// Initialize once
licensing.init({
publicKey: 'pk_live_xxxxx',
productSlug: 'my-app',
});
// Use anywhere
await licensing.validate('IRON-XXXX-XXXX-XXXX-XXXX');
if (licensing.hasFeature('premium')) {
// Premium features
}
console.log('Status:', licensing.getStatus());
console.log('Licensed:', licensing.isLicensed());
```
## Configuration
```typescript
import { LicenseClient } from '@ironservices/licensing';
const client = new LicenseClient({
publicKey: 'pk_live_xxxxx', // Required
productSlug: 'my-app', // Required
apiBaseUrl: 'https://api.ironlicensing.com',
debug: false,
enableOfflineCache: true,
cacheValidationMinutes: 60,
offlineGraceDays: 7,
storageKeyPrefix: 'ironlicensing',
});
```
### Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `publicKey` | string | required | Your public key (pk_live_xxx or pk_test_xxx) |
| `productSlug` | string | required | Your product slug |
| `apiBaseUrl` | string | https://api.ironlicensing.com | API base URL |
| `debug` | boolean | false | Enable debug logging |
| `enableOfflineCache` | boolean | true | Cache validation results |
| `cacheValidationMinutes` | number | 60 | Cache validity in minutes |
| `offlineGraceDays` | number | 7 | Offline grace period in days |
| `storageKeyPrefix` | string | 'ironlicensing' | Storage key prefix |
## License Status
```typescript
type LicenseStatus =
| 'valid' // License is valid
| 'expired' // License has expired
| 'suspended' // License is suspended
| 'revoked' // License is revoked
| 'invalid' // License key is invalid
| 'trial' // Trial license
| 'trial_expired' // Trial has expired
| 'not_activated' // No license activated
| 'unknown'; // Unknown status
```
## Features
### Check Features
```typescript
// Check if feature is enabled
if (client.hasFeature('premium')) {
enablePremiumFeatures();
}
// Get feature details
const feature = client.getFeature('max-users');
if (feature?.enabled) {
console.log('Max users:', feature.metadata?.limit);
}
// Get all features
const features = client.getFeatures();
features.forEach(f => {
console.log(`${f.name}: ${f.enabled ? 'enabled' : 'disabled'}`);
});
```
### Require Features
```typescript
import { LicenseRequiredError } from '@ironservices/licensing';
try {
client.requireFeature('enterprise');
// This code only runs if feature is available
} catch (e) {
if (e instanceof LicenseRequiredError) {
showUpgradeDialog();
}
}
```
## Trial Management
```typescript
// Start a trial
const result = await client.startTrial('user@example.com');
if (result.valid) {
console.log('Trial started!');
console.log('Expires:', result.license?.expiresAt);
}
// Check if trial
if (client.isTrial) {
showTrialBanner(client.expiresAt);
}
```
## Purchase Flow
```typescript
// Get available tiers
const tiers = await client.getTiers();
tiers.forEach(tier => {
console.log(`${tier.name}: ${tier.price} ${tier.currency}/${tier.billingPeriod}`);
});
// Start checkout
const checkout = await client.startPurchase('tier-pro', 'user@example.com');
if (checkout.success) {
// Redirect to checkout URL
window.location.href = checkout.checkoutUrl!;
}
```
## Deactivation
```typescript
// Deactivate on this machine
const success = await client.deactivate();
if (success) {
console.log('License deactivated');
}
```
## License Change Events
```typescript
// Listen for license changes
const unsubscribe = client.onLicenseChange((license) => {
if (license) {
console.log('License updated:', license.status);
} else {
console.log('License removed');
}
});
// Later: unsubscribe
unsubscribe();
```
## Offline Support
The SDK automatically caches validation results and supports offline usage:
```typescript
const client = new LicenseClient({
publicKey: 'pk_live_xxxxx',
productSlug: 'my-app',
enableOfflineCache: true,
cacheValidationMinutes: 60, // Use cache for 60 minutes
offlineGraceDays: 7, // Allow 7 days offline
});
// This will use cache if available
const result = await client.validate();
if (result.cached) {
console.log('Using cached validation');
}
```
## Custom Storage
```typescript
import { LicenseClient, StorageAdapter, LicenseCache } from '@ironservices/licensing';
// Implement custom storage
class MyStorage implements StorageAdapter {
get(key: string): string | null {
return myDatabase.get(key);
}
set(key: string, value: string): void {
myDatabase.set(key, value);
}
remove(key: string): void {
myDatabase.delete(key);
}
}
```
## TypeScript Support
Full TypeScript support with exported types:
```typescript
import type {
LicenseOptions,
License,
LicenseResult,
LicenseStatus,
LicenseType,
Feature,
Activation,
CheckoutResult,
ProductTier,
} from '@ironservices/licensing';
```
## Browser & Node.js
The SDK works in both browser and Node.js environments:
- **Browser**: Uses `localStorage` for caching
- **Node.js**: Uses in-memory storage by default
## Links
- [Documentation](https://www.ironlicensing.com/docs)
- [Dashboard](https://www.ironlicensing.com)
## License
MIT License - see [LICENSE](LICENSE) for details.

44
package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "@ironservices/licensing",
"version": "1.0.0",
"description": "Software licensing and activation SDK for JavaScript/TypeScript",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
"typecheck": "tsc --noEmit",
"prepublishOnly": "npm run build"
},
"keywords": [
"licensing",
"activation",
"license-key",
"software-licensing",
"feature-flags",
"trial",
"subscription"
],
"author": "IronServices",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/IronServices/ironlicensing-js.git"
},
"homepage": "https://www.ironlicensing.com",
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.3.0"
}
}

286
src/client.ts Normal file
View File

@ -0,0 +1,286 @@
import type {
LicenseOptions,
License,
LicenseResult,
LicenseStatus,
CheckoutResult,
ProductTier,
Feature,
LicenseChangeCallback,
} from './types';
import { Transport } from './transport';
import { LicenseCache, getDefaultStorage } from './storage';
/**
* Default configuration options.
*/
const DEFAULT_OPTIONS: Partial<LicenseOptions> = {
apiBaseUrl: 'https://api.ironlicensing.com',
debug: false,
enableOfflineCache: true,
cacheValidationMinutes: 60,
offlineGraceDays: 7,
storageKeyPrefix: 'ironlicensing',
};
/**
* IronLicensing client for license validation and activation.
*/
export class LicenseClient {
private options: Required<LicenseOptions>;
private transport: Transport;
private cache: LicenseCache;
private currentLicense: License | null = null;
private onLicenseChangeCallbacks: LicenseChangeCallback[] = [];
constructor(options: LicenseOptions) {
this.options = { ...DEFAULT_OPTIONS, ...options } as Required<LicenseOptions>;
if (!this.options.publicKey) {
throw new Error('Public key is required');
}
if (!this.options.productSlug) {
throw new Error('Product slug is required');
}
this.transport = new Transport(
this.options.apiBaseUrl,
this.options.publicKey,
this.options.productSlug,
this.options.debug,
);
this.cache = new LicenseCache(getDefaultStorage(), this.options.storageKeyPrefix);
// Load cached license
this.currentLicense = this.cache.getLicense();
if (this.options.debug) {
console.log('[IronLicensing] Client initialized');
}
}
/**
* Validates the current or provided license key.
*/
async validate(licenseKey?: string): Promise<LicenseResult> {
const key = licenseKey || this.cache.getLicenseKey();
if (!key) {
return { valid: false, error: 'No license key provided' };
}
// Check cache first
if (this.options.enableOfflineCache) {
const cached = this.getCachedValidation();
if (cached) {
return { ...cached, cached: true };
}
}
// Validate online
const result = await this.transport.validate(key);
if (result.valid && result.license) {
this.setLicense(result.license, key);
this.cache.setValidationResult(result, new Date());
}
return result;
}
/**
* Activates a license.
*/
async activate(licenseKey: string, machineName?: string): Promise<LicenseResult> {
const result = await this.transport.activate(licenseKey, machineName);
if (result.valid && result.license) {
this.setLicense(result.license, licenseKey);
this.cache.setValidationResult(result, new Date());
}
return result;
}
/**
* Deactivates the current license.
*/
async deactivate(): Promise<boolean> {
const key = this.cache.getLicenseKey();
if (!key) return false;
const success = await this.transport.deactivate(key);
if (success) {
this.setLicense(null, null);
this.cache.clear();
}
return success;
}
/**
* Starts a trial.
*/
async startTrial(email: string): Promise<LicenseResult> {
const result = await this.transport.startTrial(email);
if (result.valid && result.license) {
this.setLicense(result.license, result.license.key);
this.cache.setValidationResult(result, new Date());
}
return result;
}
/**
* Checks if a feature is enabled.
*/
hasFeature(featureKey: string): boolean {
if (!this.currentLicense) return false;
const feature = this.currentLicense.features.find(f => f.key === featureKey);
return feature?.enabled ?? false;
}
/**
* Requires a feature, throwing if not available.
*/
requireFeature(featureKey: string): void {
if (!this.hasFeature(featureKey)) {
throw new LicenseRequiredError(`Feature '${featureKey}' requires a valid license`);
}
}
/**
* Gets a feature by key.
*/
getFeature(featureKey: string): Feature | undefined {
return this.currentLicense?.features.find(f => f.key === featureKey);
}
/**
* Gets all features.
*/
getFeatures(): Feature[] {
return this.currentLicense?.features || [];
}
/**
* Gets the current license.
*/
get license(): License | null {
return this.currentLicense;
}
/**
* Gets the current license status.
*/
get status(): LicenseStatus {
return this.currentLicense?.status ?? 'not_activated';
}
/**
* Checks if the license is valid.
*/
get isLicensed(): boolean {
return this.currentLicense?.status === 'valid' || this.currentLicense?.status === 'trial';
}
/**
* Checks if the license is a trial.
*/
get isTrial(): boolean {
return this.currentLicense?.status === 'trial' || this.currentLicense?.type === 'trial';
}
/**
* Gets the license expiration date.
*/
get expiresAt(): Date | undefined {
return this.currentLicense?.expiresAt;
}
/**
* Gets product tiers for purchase.
*/
async getTiers(): Promise<ProductTier[]> {
return this.transport.getTiers();
}
/**
* Starts a purchase checkout.
*/
async startPurchase(tierId: string, email: string): Promise<CheckoutResult> {
return this.transport.startCheckout(tierId, email);
}
/**
* Registers a callback for license changes.
*/
onLicenseChange(callback: LicenseChangeCallback): () => void {
this.onLicenseChangeCallbacks.push(callback);
return () => {
const index = this.onLicenseChangeCallbacks.indexOf(callback);
if (index > -1) {
this.onLicenseChangeCallbacks.splice(index, 1);
}
};
}
private setLicense(license: License | null, key: string | null): void {
this.currentLicense = license;
if (license) {
this.cache.setLicense(license);
}
if (key) {
this.cache.setLicenseKey(key);
}
// Notify listeners
for (const callback of this.onLicenseChangeCallbacks) {
try {
callback(license);
} catch (e) {
console.error('[IronLicensing] Error in license change callback:', e);
}
}
}
private getCachedValidation(): LicenseResult | null {
const cached = this.cache.getValidationResult();
const time = this.cache.getValidationTime();
if (!cached || !time) return null;
// Check if cache is still valid
const cacheAge = Date.now() - time.getTime();
const maxAge = this.options.cacheValidationMinutes * 60 * 1000;
if (cacheAge <= maxAge) {
return cached;
}
// Check offline grace period
const graceAge = this.options.offlineGraceDays * 24 * 60 * 60 * 1000;
if (cacheAge <= graceAge && cached.valid) {
if (this.options.debug) {
console.log('[IronLicensing] Using offline grace period');
}
return cached;
}
return null;
}
}
/**
* Error thrown when a license or feature is required but not available.
*/
export class LicenseRequiredError extends Error {
constructor(message: string) {
super(message);
this.name = 'LicenseRequiredError';
}
}

171
src/index.ts Normal file
View File

@ -0,0 +1,171 @@
/**
* IronLicensing SDK for JavaScript/TypeScript
*
* Software licensing and activation SDK.
*
* @example
* ```typescript
* import { LicenseClient } from '@ironservices/licensing';
*
* const client = new LicenseClient({
* publicKey: 'pk_live_xxxxx',
* productSlug: 'my-app',
* });
*
* // Validate license
* const result = await client.validate('IRON-XXXX-XXXX-XXXX-XXXX');
* if (result.valid) {
* console.log('License is valid!');
* }
*
* // Check features
* if (client.hasFeature('premium')) {
* // Enable premium features
* }
* ```
*/
export { LicenseClient, LicenseRequiredError } from './client';
export type {
LicenseOptions,
License,
LicenseResult,
LicenseStatus,
LicenseType,
Feature,
Activation,
CheckoutResult,
ProductTier,
LicenseChangeCallback,
} from './types';
export {
LicenseCache,
StorageAdapter,
MemoryStorage,
LocalStorageAdapter,
getDefaultStorage,
} from './storage';
// Global client for convenience
import { LicenseClient } from './client';
import type { LicenseOptions, LicenseResult, CheckoutResult, ProductTier, Feature, License, LicenseStatus } from './types';
let globalClient: LicenseClient | null = null;
/**
* Initializes the global license client.
*/
export function init(options: LicenseOptions): LicenseClient {
globalClient = new LicenseClient(options);
return globalClient;
}
/**
* Gets the global license client.
*/
export function getClient(): LicenseClient {
if (!globalClient) {
throw new Error('IronLicensing not initialized. Call init() first.');
}
return globalClient;
}
/**
* Validates the current or provided license key using the global client.
*/
export async function validate(licenseKey?: string): Promise<LicenseResult> {
return getClient().validate(licenseKey);
}
/**
* Activates a license using the global client.
*/
export async function activate(licenseKey: string, machineName?: string): Promise<LicenseResult> {
return getClient().activate(licenseKey, machineName);
}
/**
* Deactivates the current license using the global client.
*/
export async function deactivate(): Promise<boolean> {
return getClient().deactivate();
}
/**
* Starts a trial using the global client.
*/
export async function startTrial(email: string): Promise<LicenseResult> {
return getClient().startTrial(email);
}
/**
* Checks if a feature is enabled using the global client.
*/
export function hasFeature(featureKey: string): boolean {
return getClient().hasFeature(featureKey);
}
/**
* Requires a feature using the global client.
*/
export function requireFeature(featureKey: string): void {
getClient().requireFeature(featureKey);
}
/**
* Gets a feature by key using the global client.
*/
export function getFeature(featureKey: string): Feature | undefined {
return getClient().getFeature(featureKey);
}
/**
* Gets all features using the global client.
*/
export function getFeatures(): Feature[] {
return getClient().getFeatures();
}
/**
* Gets the current license using the global client.
*/
export function getLicense(): License | null {
return getClient().license;
}
/**
* Gets the license status using the global client.
*/
export function getStatus(): LicenseStatus {
return getClient().status;
}
/**
* Checks if the license is valid using the global client.
*/
export function isLicensed(): boolean {
return getClient().isLicensed;
}
/**
* Checks if the license is a trial using the global client.
*/
export function isTrial(): boolean {
return getClient().isTrial;
}
/**
* Gets product tiers using the global client.
*/
export async function getTiers(): Promise<ProductTier[]> {
return getClient().getTiers();
}
/**
* Starts a purchase using the global client.
*/
export async function startPurchase(tierId: string, email: string): Promise<CheckoutResult> {
return getClient().startPurchase(tierId, email);
}

172
src/storage.ts Normal file
View File

@ -0,0 +1,172 @@
import type { License, LicenseResult } from './types';
/**
* Storage interface for license caching.
*/
export interface StorageAdapter {
get(key: string): string | null;
set(key: string, value: string): void;
remove(key: string): void;
}
/**
* In-memory storage adapter.
*/
export class MemoryStorage implements StorageAdapter {
private data: Map<string, string> = new Map();
get(key: string): string | null {
return this.data.get(key) ?? null;
}
set(key: string, value: string): void {
this.data.set(key, value);
}
remove(key: string): void {
this.data.delete(key);
}
}
/**
* LocalStorage adapter for browser.
*/
export class LocalStorageAdapter implements StorageAdapter {
get(key: string): string | null {
try {
return localStorage.getItem(key);
} catch {
return null;
}
}
set(key: string, value: string): void {
try {
localStorage.setItem(key, value);
} catch {
// Ignore storage errors
}
}
remove(key: string): void {
try {
localStorage.removeItem(key);
} catch {
// Ignore storage errors
}
}
}
/**
* License cache manager.
*/
export class LicenseCache {
private storage: StorageAdapter;
private keyPrefix: string;
constructor(storage: StorageAdapter, keyPrefix: string = 'ironlicensing') {
this.storage = storage;
this.keyPrefix = keyPrefix;
}
private key(name: string): string {
return `${this.keyPrefix}:${name}`;
}
/**
* Gets the cached license.
*/
getLicense(): License | null {
const data = this.storage.get(this.key('license'));
if (!data) return null;
try {
const parsed = JSON.parse(data);
// Restore Date objects
if (parsed.expiresAt) parsed.expiresAt = new Date(parsed.expiresAt);
if (parsed.createdAt) parsed.createdAt = new Date(parsed.createdAt);
if (parsed.lastValidatedAt) parsed.lastValidatedAt = new Date(parsed.lastValidatedAt);
return parsed;
} catch {
return null;
}
}
/**
* Sets the cached license.
*/
setLicense(license: License): void {
this.storage.set(this.key('license'), JSON.stringify(license));
}
/**
* Gets the cached validation result.
*/
getValidationResult(): LicenseResult | null {
const data = this.storage.get(this.key('validation'));
if (!data) return null;
try {
const parsed = JSON.parse(data);
if (parsed.license) {
if (parsed.license.expiresAt) parsed.license.expiresAt = new Date(parsed.license.expiresAt);
if (parsed.license.createdAt) parsed.license.createdAt = new Date(parsed.license.createdAt);
if (parsed.license.lastValidatedAt) parsed.license.lastValidatedAt = new Date(parsed.license.lastValidatedAt);
}
return parsed;
} catch {
return null;
}
}
/**
* Sets the cached validation result.
*/
setValidationResult(result: LicenseResult, timestamp: Date): void {
this.storage.set(this.key('validation'), JSON.stringify(result));
this.storage.set(this.key('validation_time'), timestamp.toISOString());
}
/**
* Gets the validation timestamp.
*/
getValidationTime(): Date | null {
const data = this.storage.get(this.key('validation_time'));
if (!data) return null;
return new Date(data);
}
/**
* Gets the stored license key.
*/
getLicenseKey(): string | null {
return this.storage.get(this.key('license_key'));
}
/**
* Sets the license key.
*/
setLicenseKey(key: string): void {
this.storage.set(this.key('license_key'), key);
}
/**
* Clears all cached data.
*/
clear(): void {
this.storage.remove(this.key('license'));
this.storage.remove(this.key('validation'));
this.storage.remove(this.key('validation_time'));
this.storage.remove(this.key('license_key'));
}
}
/**
* Gets the default storage adapter based on environment.
*/
export function getDefaultStorage(): StorageAdapter {
if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') {
return new LocalStorageAdapter();
}
return new MemoryStorage();
}

233
src/transport.ts Normal file
View File

@ -0,0 +1,233 @@
import type { License, LicenseResult, CheckoutResult, ProductTier, Activation } from './types';
/**
* HTTP transport for IronLicensing API.
*/
export class Transport {
private baseUrl: string;
private publicKey: string;
private productSlug: string;
private debug: boolean;
constructor(baseUrl: string, publicKey: string, productSlug: string, debug: boolean) {
this.baseUrl = baseUrl;
this.publicKey = publicKey;
this.productSlug = productSlug;
this.debug = debug;
}
private log(message: string): void {
if (this.debug) {
console.log(`[IronLicensing] ${message}`);
}
}
private async request<T>(
method: string,
path: string,
body?: unknown,
): Promise<T> {
const url = `${this.baseUrl}${path}`;
this.log(`${method} ${path}`);
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'X-Public-Key': this.publicKey,
'X-Product-Slug': this.productSlug,
},
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: `HTTP ${response.status}` }));
throw new Error(error.error || `HTTP ${response.status}`);
}
return response.json();
}
private parseLicense(data: any): License {
return {
...data,
expiresAt: data.expiresAt ? new Date(data.expiresAt) : undefined,
createdAt: new Date(data.createdAt),
lastValidatedAt: data.lastValidatedAt ? new Date(data.lastValidatedAt) : undefined,
};
}
private parseActivation(data: any): Activation {
return {
...data,
activatedAt: new Date(data.activatedAt),
lastSeenAt: new Date(data.lastSeenAt),
};
}
/**
* Validates a license key.
*/
async validate(licenseKey: string): Promise<LicenseResult> {
try {
const result = await this.request<any>('POST', '/api/v1/validate', {
licenseKey,
machineId: this.getMachineId(),
});
return {
valid: result.valid,
license: result.license ? this.parseLicense(result.license) : undefined,
activations: result.activations?.map((a: any) => this.parseActivation(a)),
cached: false,
};
} catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : 'Validation failed',
};
}
}
/**
* Activates a license.
*/
async activate(licenseKey: string, machineName?: string): Promise<LicenseResult> {
try {
const result = await this.request<any>('POST', '/api/v1/activate', {
licenseKey,
machineId: this.getMachineId(),
machineName: machineName || this.getMachineName(),
platform: this.getPlatform(),
});
return {
valid: result.valid,
license: result.license ? this.parseLicense(result.license) : undefined,
activations: result.activations?.map((a: any) => this.parseActivation(a)),
};
} catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : 'Activation failed',
};
}
}
/**
* Deactivates a license.
*/
async deactivate(licenseKey: string): Promise<boolean> {
try {
await this.request('POST', '/api/v1/deactivate', {
licenseKey,
machineId: this.getMachineId(),
});
return true;
} catch {
return false;
}
}
/**
* Starts a trial.
*/
async startTrial(email: string): Promise<LicenseResult> {
try {
const result = await this.request<any>('POST', '/api/v1/trial', {
email,
machineId: this.getMachineId(),
});
return {
valid: result.valid,
license: result.license ? this.parseLicense(result.license) : undefined,
};
} catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : 'Failed to start trial',
};
}
}
/**
* Gets product tiers.
*/
async getTiers(): Promise<ProductTier[]> {
try {
const result = await this.request<{ tiers: ProductTier[] }>('GET', '/api/v1/tiers');
return result.tiers || [];
} catch {
return [];
}
}
/**
* Starts a checkout session.
*/
async startCheckout(tierId: string, email: string): Promise<CheckoutResult> {
try {
const result = await this.request<CheckoutResult>('POST', '/api/v1/checkout', {
tierId,
email,
});
return result;
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Checkout failed',
};
}
}
/**
* Checks if the API is online.
*/
async isOnline(): Promise<boolean> {
try {
const response = await fetch(`${this.baseUrl}/health`);
return response.ok;
} catch {
return false;
}
}
private getMachineId(): string {
// Try to get a persistent machine ID
if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') {
let id = localStorage.getItem('ironlicensing:machine_id');
if (!id) {
id = this.generateId();
localStorage.setItem('ironlicensing:machine_id', id);
}
return id;
}
// For non-browser environments, generate a random ID
return this.generateId();
}
private getMachineName(): string {
if (typeof navigator !== 'undefined') {
return navigator.userAgent.substring(0, 100);
}
return 'Unknown';
}
private getPlatform(): string {
if (typeof navigator !== 'undefined') {
return navigator.platform || 'web';
}
return 'unknown';
}
private generateId(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
}

123
src/types.ts Normal file
View File

@ -0,0 +1,123 @@
/**
* License status.
*/
export type LicenseStatus =
| 'valid'
| 'expired'
| 'suspended'
| 'revoked'
| 'invalid'
| 'trial'
| 'trial_expired'
| 'not_activated'
| 'unknown';
/**
* License type.
*/
export type LicenseType = 'perpetual' | 'subscription' | 'trial';
/**
* Configuration options for the LicenseClient.
*/
export interface LicenseOptions {
/** Public key for validation (required). Format: pk_live_xxx or pk_test_xxx */
publicKey: string;
/** Product slug (required) */
productSlug: string;
/** API base URL */
apiBaseUrl?: string;
/** Enable debug logging */
debug?: boolean;
/** Enable offline validation caching */
enableOfflineCache?: boolean;
/** Cache validation for this many minutes */
cacheValidationMinutes?: number;
/** Offline grace period in days */
offlineGraceDays?: number;
/** Storage key prefix */
storageKeyPrefix?: string;
}
/**
* Feature in a license.
*/
export interface Feature {
key: string;
name: string;
description?: string;
enabled: boolean;
metadata?: Record<string, unknown>;
}
/**
* License information.
*/
export interface License {
id: string;
key: string;
status: LicenseStatus;
type: LicenseType;
email?: string;
name?: string;
company?: string;
features: Feature[];
maxActivations: number;
currentActivations: number;
expiresAt?: Date;
createdAt: Date;
lastValidatedAt?: Date;
metadata?: Record<string, unknown>;
}
/**
* Activation information.
*/
export interface Activation {
id: string;
machineId: string;
machineName?: string;
platform?: string;
activatedAt: Date;
lastSeenAt: Date;
}
/**
* Result of license validation.
*/
export interface LicenseResult {
valid: boolean;
license?: License;
activations?: Activation[];
error?: string;
cached?: boolean;
}
/**
* Result of checkout.
*/
export interface CheckoutResult {
success: boolean;
checkoutUrl?: string;
sessionId?: string;
error?: string;
}
/**
* Product tier for purchase.
*/
export interface ProductTier {
id: string;
slug: string;
name: string;
description?: string;
price: number;
currency: string;
billingPeriod?: 'monthly' | 'yearly' | 'lifetime';
features: Feature[];
}
/**
* Callback for license changes.
*/
export type LicenseChangeCallback = (license: License | null) => void;

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}