@dits-sa/license-client
v0.1.0
Published
Official TypeScript SDK for the DITS License Server — activation, sync, session management, and offline verification.
Maintainers
Readme
@dits-sa/license-client
Official TypeScript SDK for the DITS License Server. Handles license activation, sync, session management, offline verification, and feature flags.
Works in any JavaScript runtime (browser, Node.js, Electron, Tauri, Deno, Bun). React bindings available via @dits-sa/license-client/react.
Install
npm install @dits-sa/license-client
# or
pnpm add @dits-sa/license-clientQuick Start
Inertia + Laravel (server-provided license)
import { createLicenseClient } from '@dits-sa/license-client'
import { LicenseProvider } from '@dits-sa/license-client/react'
// Laravel passes license + fingerprint via Inertia shared props
function App({ licenseToken, machineHash, ...pageProps }) {
const client = useMemo(() => createLicenseClient({
baseUrl: 'https://license.yourcompany.com',
apiKey: import.meta.env.VITE_LICENSE_API_KEY,
license: licenseToken,
fingerprint: { hash: machineHash },
}), [licenseToken, machineHash])
return (
<LicenseProvider client={client}>
<YourApp {...pageProps} />
</LicenseProvider>
)
}Standalone SPA
import { createLicenseClient, LocalStorageLicenseStore } from '@dits-sa/license-client'
import { LicenseProvider, LicenseGuard } from '@dits-sa/license-client/react'
const client = createLicenseClient({
baseUrl: 'https://license.yourcompany.com',
apiKey: import.meta.env.VITE_LICENSE_API_KEY,
store: new LocalStorageLicenseStore(),
})
function App() {
return (
<LicenseProvider client={client}>
<LicenseGuard fallback={<ActivationScreen />}>
<YourApp />
</LicenseGuard>
</LicenseProvider>
)
}Node.js / Electron (no React)
import { createLicenseClient, MemoryLicenseStore } from '@dits-sa/license-client'
const client = createLicenseClient({
baseUrl: 'https://license.yourcompany.com',
apiKey: process.env.LICENSE_API_KEY,
store: new MemoryLicenseStore(),
})
const { license } = await client.activate({
serial: 'XXXX-XXXX-XXXX',
activationCode: '123456',
fingerprint: { cpu: 'Intel', board: 'ASUS' },
hash: 'sha256-of-fingerprint',
instanceKey: 'unique-instance-id',
})API Reference
Core Client
createLicenseClient(config)
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| baseUrl | string | Yes | License server URL |
| apiKey | string | Yes | API key (X-Api-Key header) |
| timeout | number | No | Request timeout in ms (default: 30000) |
| retry | { maxRetries, backoff } | No | Retry config (default: 3 retries, exponential) |
| fingerprint | object \| function | No | Server-provided { hash } or async collector function |
| license | string | No | Pre-loaded base64 license token (Inertia/Tauri) |
| store | LicenseStore | No | Persistent storage for SPAs |
| onError | function | No | Error callback |
| fetch | function | No | Custom fetch implementation |
Methods
client.activate(params) // Activate a license
client.sync({ serial, hash }) // Sync / refresh license token
client.sessions.start({ serial, hash }) // Start concurrent session
client.sessions.heartbeat({ sessionToken }) // Keep session alive
client.sessions.end({ sessionToken }) // End session
client.parseLicense(token?) // Parse cached license token
client.getLicense() // Get raw cached tokenLicense Parsing
import { parseLicenseToken, isLicenseExpired } from '@dits-sa/license-client'
const license = parseLicenseToken(base64Token)
// { id, type, expiresAt, maxDevices, customer, features, deviceMode, ... }
isLicenseExpired(license) // booleanStorage
import { LocalStorageLicenseStore, MemoryLicenseStore } from '@dits-sa/license-client'
new LocalStorageLicenseStore() // Browser (AES-GCM encrypted at rest)
new MemoryLicenseStore() // Node.js / testingSync Manager
import { createSyncManager } from '@dits-sa/license-client'
const manager = createSyncManager({
client,
serial: 'XXXX',
hash: 'machine-hash',
store,
interval: 30 * 60_000, // 30 min
onSync: (license) => console.log('Synced'),
onError: (err) => console.error(err),
})
manager.start() // Begin periodic sync
manager.stop() // Stop
manager.syncNow() // Force immediate syncReact Hooks
useActivateLicense()
const { mutateAsync: activate, isPending } = useActivateLicense()
await activate({ serial, activationCode, fingerprint, hash, instanceKey })useSyncLicense({ serial, hash, interval? })
Auto-refreshing query with configurable interval (default 30 min).
useSessionManager({ serial, hash, heartbeatInterval? })
Full session lifecycle: auto-start, heartbeat, cleanup on unmount/beforeunload.
const { status, sessionToken, start, end } = useSessionManager({ serial, hash })
// status: 'idle' | 'starting' | 'active' | 'expired' | 'error'useLicense() / useLicenseStatus() / useFeatureFlag(name)
const license = useLicense() // ParsedLicense | null
const status = useLicenseStatus() // 'valid' | 'expired' | 'none'
const hasAnalytics = useFeatureFlag('advanced-analytics') // booleanReact Guards
Mirror Laravel middleware — conditionally render based on license state.
<LicenseGuard fallback={<ActivationScreen />}>
<FeatureGuard feature="analytics" fallback={<UpgradePrompt />}>
<SessionGuard serial={s} hash={h} fallback={<SessionLimitScreen />}>
<App />
</SessionGuard>
</FeatureGuard>
</LicenseGuard>Error Handling
All API errors throw typed exceptions:
| Error | Code | HTTP |
|-------|------|------|
| LicenseNotFoundError | LICENSE_NOT_FOUND | 404 |
| LicenseExpiredError | LICENSE_EXPIRED | 403 |
| LicenseRevokedError | LICENSE_REVOKED | 403 |
| InvalidActivationCodeError | INVALID_ACTIVATION_CODE | 403 |
| DeviceLimitError | LICENSE_DEVICE_LIMIT | 409 |
| SessionLimitError | SESSION_LIMIT | 409 |
| SessionExpiredError | SESSION_EXPIRED | 401 |
| RateLimitError | RATE_LIMIT_EXCEEDED | 429 |
| ValidationError | VALIDATION_ERROR | 422 |
| SignatureVerificationError | SIGNATURE_INVALID | - |
| NetworkError | NETWORK_ERROR | - |
try {
await client.activate(params)
} catch (err) {
if (err instanceof DeviceLimitError) {
// Handle max device limit
}
}Security Model
The SDK is built with defense-in-depth against common license cracking techniques:
- Embedded public keys: ECDSA verification keys are hardcoded in the SDK, not configurable via env vars. Prevents key replacement attacks.
- Signed API responses: Every response includes an ECDSA signature (
X-Signatureheader) verified against the embedded key. Prevents server spoofing. - Nonce + timestamp: Each request includes a unique nonce echoed in the signed response. Rejects replayed or stale responses (>5 min).
- Anti-clock-rollback: Stores last verified server timestamp. Detects system clock manipulation (>24h drift).
- Encrypted storage:
LocalStorageLicenseStoreuses AES-GCM encryption tied to the browser origin. - Integrity self-checks: Critical verification functions are SHA-256 hashed at build time and checked at runtime.
- Key rotation: Supports multiple trusted keys via
kid(key ID) for seamless server key rotation. - Server is the authority: All client-side checks are defense-in-depth. The server enforces device limits, session limits, and revocation.
License
MIT
