@cmsx-dev/sdk
License validation SDK for Node.js, Electron, and server-side JavaScript applications. Handles API calls, machine fingerprinting, Ed25519 signature verification, and offline caching out of the box.
Installation
npm install @cmsx-dev/sdkOr with other package managers:
pnpm add @cmsx-dev/sdk
yarn add @cmsx-dev/sdk
bun add @cmsx-dev/sdkQuick Start
import { CMSXClient } from '@cmsx-dev/sdk'
const cmsx = new CMSXClient({
apiKey: 'xk_xxxxxxxxxxxxx',
licenseKey: 'XCR-XXXX-XXXX-XXXX',
publicKey: 'ed25519_hex_64chars', // optional
})
const result = await cmsx.validate()
if (result.valid) {
console.log('License active!')
console.log('Config:', result.config)
} else {
console.log('License invalid:', result.status)
process.exit(1)
}Configuration
The CMSXConfig interface accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | required | Your API key (starts with xk_) |
licenseKey | string | required | License key (XCR-XXXX-XXXX-XXXX) |
publicKey | string | — | Ed25519 public key for signature verification (hex, 64 chars) |
baseUrl | string | https://api.cmsx.dev | API base URL |
gracePeriod | number | 259200 | Seconds a cached response is trusted offline (72h) |
cachePath | string | ~/.cache/cmsx/ | Custom path for the cache file (Node.js only) |
Methods
validate()Promise<ValidationResponse>Validates the license against the CMSX API. If the server is unreachable, falls back to a locally cached response (if within grace period).
const result = await cmsx.validate()
// result.valid — boolean
// result.licenseId — string | null
// result.productId — string | null
// result.status — "active" | "expired" | "revoked" | "killed" | ...
// result.config — Record<string, string>
// result.signature — Ed25519 base64 signature
// result.validatedAt — ISO 8601 timestampdeactivate()Promise<void>Deactivates this machine from the license. Decrements the activation counter on the server and removes the local cache. Call this when the user uninstalls your app.
await cmsx.deactivate()
console.log('Machine deactivated, license slot freed')isValid()booleanReturns the last known validation state synchronously (no network call). Useful for cache checks between validations.
if (cmsx.isValid()) {
// proceed with the app
}getConfig()Record<string, string>Returns the last known config object synchronously. Use this to read feature flags or tier information set in the CMSX dashboard.
const config = cmsx.getConfig()
const tier = config['tier'] // e.g., "pro"
const maxUsers = config['max_users'] // e.g., "50"machineIdstringThe SHA-256 machine fingerprint (read-only property). This is the unique identifier sent with each validation request.
console.log(cmsx.machineId)
// "a1b2c3d4e5f6..." (64-char hex SHA-256)Machine Fingerprint
The SDK automatically generates a deterministic SHA-256 fingerprint from system properties:
- hostname — OS hostname
- platform — e.g., linux, darwin, win32
- arch — e.g., x64, arm64
- username — OS user running the process
The same machine always produces the same fingerprint. You can also use generateMachineId() standalone:
import { generateMachineId } from '@cmsx-dev/sdk'
const id = generateMachineId()
console.log(id) // "648a28cfd782ef38..." (64-char hex)Signature Verification
Every valid response includes an Ed25519 signature. The SDK verifies it automatically when you provide a publicKey in the config. You can also verify manually:
import { verifySignature } from '@cmsx-dev/sdk'
const payload = {
valid: true,
license_id: "6c9885fc-...",
product_id: "e51cd7a3-...",
status: "active",
validated_at: "2026-02-27T01:41:10.044Z",
}
const isValid = await verifySignature(
payload,
result.signature, // base64 string
'your_public_key_hex' // 64-char hex
)
if (!isValid) {
throw new Error('Response tampered!')
}Uses @noble/ed25519 — a pure JS, zero-dependency, audited implementation.
Offline Mode
When the API is unreachable, the SDK automatically falls back to a locally cached response. The cache is valid for the gracePeriod (default: 72 hours).
const cmsx = new CMSXClient({
apiKey: 'xk_xxxxxxxxxxxxx',
licenseKey: 'XCR-XXXX-XXXX-XXXX',
gracePeriod: 86400, // 24 hours
})
// First call: hits API, caches response
await cmsx.validate()
// Later, if offline: returns cached result
// as long as it's within 24 hours
await cmsx.validate()Cache location
By default, cached responses are stored at ~/.cache/cmsx/<key>.json. Customize with the cachePath option.
@cmsx-dev/next
Drop-in Next.js middleware for license validation. Validate your CMSX license at the edge with zero client-side code. Results are cached in an httpOnly cookie and re-validated on a configurable interval.
Installation
npm install @cmsx-dev/nextThis package depends on @cmsx-dev/sdk and is designed for Next.js 13+ (App Router).
Quick Start
Create a middleware.ts at the root of your Next.js project:
// middleware.ts
import { withCMSX } from '@cmsx-dev/next'
export default withCMSX({
apiKey: process.env.CMSX_API_KEY!,
licenseKey: process.env.CMSX_LICENSE_KEY!,
protectedRoutes: ['/dashboard/*', '/api/*'],
onInvalid: '/license-expired',
})
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
}Add environment variables to your .env.local:
CMSX_API_KEY=xk_xxxxxxxxxxxxx
CMSX_LICENSE_KEY=XCR-XXXX-XXXX-XXXXConfiguration
The CMSXMiddlewareConfig interface:
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | required | Your CMSX API key (xk_...) |
licenseKey | string | required | The license key to validate |
publicKey | string | — | Ed25519 public key for verification (hex) |
baseUrl | string | https://api.cmsx.dev | API base URL |
protectedRoutes | string[] | ["/*"] | Glob patterns for protected routes |
onInvalid | string | 403 response | Route to redirect to when invalid |
revalidateInterval | number | 300 | Seconds between re-validations (5 min) |
Behavior
1. Route Matching
Only routes matching protectedRoutes patterns are validated. All other routes pass through.
2. Cookie Caching
After a successful validation, the result is stored in an httpOnly cookie (__cmsx_status). Subsequent requests use this cached value until it expires after revalidateInterval seconds.
3. Re-validation
When the cookie expires, the middleware calls the API again. If the license is now invalid, the user is redirected to onInvalid or receives a 403.
4. Network Errors
If the API is unreachable and a valid cached cookie exists, the request is allowed through (graceful degradation). Otherwise, the request is blocked.