npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@casazium/license-sdk

v0.5.1

Published

SDK and CLI for Casazium License Server. Supports activation, validation, and offline license checks.

Readme

@casazium/license-sdk

A lightweight JavaScript SDK for interacting with the Casazium License Server.
Supports online activation, key verification, license file validation, and optional offline enforcement via system fingerprinting.

Docs: docs.casazium.com · Guides: Licensing an Electron or Tauri desktop app, Limit a license key to a set number of computers, all guides


✨ Features

  • 🔐 Activate and deactivate licenses on a specific server or device
  • ✅ Verify license keys and signed license files
  • 🖥️ Lock licenses to system fingerprints (machineId)
  • 📁 Offline license file validation with RSA signatures
  • 🧪 Fully tested and production-ready

📦 Installation

npm install @casazium/license-sdk

Server version. SDK 0.5.0 and later need License Server 1.21.0 or later: exportLicense() and exportOfflineLicense() send the license key in the request body, which older servers don't accept (they answer 404). Hosted accounts already run 1.21.0. If you run the server yourself, upgrade it before you ship an app built on 0.5.0. Apps on SDK 0.4.x and earlier keep working with 1.21.0.

To upgrade from 0.4.x: update the server to 1.21.0 or later, then change your dependency to ^0.5.0 (^0.4.x won't pick it up). No code changes.

Against an older server, both export methods throw 404 with the message exportLicense failed with 404: Not Found (or exportOfflineLicense …). That means the server doesn't have the route, not that the key was refused: an unknown key says License key not found. Don't treat it as a refused license (see Errors); upgrade the server.


🚀 Usage

1. Instantiate the Client

import { CasaziumLicenseClient } from '@casazium/license-sdk';

const client = new CasaziumLicenseClient({
  baseUrl: 'https://your-license-server.com/v1',
});

Requests are retried on a network error or a 5xx, up to retries times (default 3). When the retries run out, the error carries the server's status and message. activate() and trackUsage() are never retried: if the server saved the activation or counted the usage but the response was lost, a retry would lose the activation token or count the usage twice.

The SDK sets no request timeout of its own. Pass a fetch that adds one, so a server that never answers can't hold up your app's start:

const client = new CasaziumLicenseClient({
  baseUrl: 'https://your-license-server.com/v1',
  retries: 1,
  fetch: (url, init) => fetch(url, { ...init, signal: AbortSignal.timeout(5000) }),
});

2. Activate a License

import { getFingerprint } from '@casazium/license-sdk/fingerprint';

const key = 'your-license-key';
const instance_id = getFingerprint();

const { activated, token } = await client.activate(key, instance_id);

// Save `token` — it's required to deactivate this specific seat later.
// It's only returned once, when the activation is first created.

If activate() fails with a network error, the activation may still have been saved. Calling it again then returns alreadyActivated with no token; an admin can issue a new one (POST /admin/reissue-token).


2a. Deactivate a License

Frees the seat so it can be activated elsewhere. Requires the token returned by the activate() call that created it — not instance_id, which is just a caller-supplied label and isn't accepted as the deactivation credential.

const { deactivated } = await client.deactivate(key, token);

3. Verify a License Key

expectedProductUuid is required — verify-license's response includes product_uuid, the license's real, immutable product identity (a product_id string alone is a per-tenant display label, not something your app can trust to distinguish "my product" from someone else's). Passing the wrong value, or a license issued for a different product, throws rather than silently returning valid: true for a license you shouldn't trust. Get your own product_uuid once (it's in the response of your first issue-license/ register-release admin call — on the hosted tier, also shown on that license's/release's detail page in the console) and hard-code it in your app — it never changes for a given product_id under your own tenant.

const result = await client.verifyKey('your-license-key', 'your-product-uuid');

if (result.valid) {
  console.log('License is valid!');
}

3a. Check This Machine Is Still Activated

verifyKey() checks the license, not this machine: it keeps returning valid: true after this machine's seat was freed somewhere else (the license portal, or a deactivate from another machine). For a launch check, also call validate() with this machine's instance id (0.4.0+):

const license = await client.verifyKey(key, 'your-product-uuid');
const { valid } = await client.validate(key, instanceId);

if (license.valid && !valid) {
  // This machine's seat was freed: ask the user to activate again.
}

validate() resolves { valid: true } only when the license is active, unexpired and activated for instanceId, and { valid: false } otherwise, without a reason.

Errors

A method that gets a non-2xx response throws an Error whose message names the method, the status and the server's own message (for example verifyKey failed with 403: License is not active), and, from 0.4.0, whose status is the HTTP status. A network failure or a timeout throws fetch's own error instead, with no status. So an app can tell "refused" from "no answer":

// The server said no: a 4xx other than 429, or verifyKey's own refusal
// of a license for another product (which has no status).
function isRefusal(err) {
  if (err.status === undefined) return /product_uuid mismatch/.test(err.message);
  // A 404 ending ": Not Found" is a route the server doesn't have (one
  // older than this SDK needs), not a refused key.
  if (err.status === 404 && /: Not Found$/.test(err.message)) return false;
  return err.status >= 400 && err.status < 500 && err.status !== 429;
}

try {
  await client.verifyKey(key, productUuid);
} catch (err) {
  if (isRefusal(err)) {
    // Revoked, expired, unknown, or another product's: treat the license
    // as invalid, and drop any offline file you keep for it.
  } else {
    // No answer: unreachable, timed out, rate limited (429) or a server
    // error. Fall back to the offline file, within your own limit.
  }
}

A 429 means "slow down", not "no": its Retry-After header says how many seconds until the rate-limit window resets, which can be up to 15 minutes.


4. Verify a Signed License File

For an app that may run offline. Export the license while online with exportOfflineLicense(key) (POST /license-export/offline, the key in the body), store the result, and check it with only the public key:

import fs from 'node:fs/promises';

// { license, signature }, as exportOfflineLicense() returned it
const file = JSON.parse(await fs.readFile('license.json', 'utf8'));

const isValid = await client.verifySignedFile(file, 'your-product-uuid');
if (!isValid) {
  // not a license for this product, tampered with, or expired
}

verifySignedFile() resolves to true only when all of these hold, and to false otherwise (it never throws on a malformed file):

  • the signature is your server's, over these exact bytes;
  • it's a license (type: "license" and a key) — not, for example, a release manifest, which is signed with the same key;
  • its product_uuid is expectedProductUuid, which is required;
  • it hasn't expired: expires_at is null (perpetual) or a date in the future.

A file exported by License Server before 1.10.0 has no type, so it's refused: export it again. Revocation can't be seen offline — a file stays valid until its own expires_at — so check online (verifyKey()) when you can, and export a fresh file after a renewal.

Fields after limits (License Server 1.20.0+). The file also signs issued_at, exported_at (the server's clock when it signed the file), livemode (true for a live purchase, false for a sandbox one) and paid_through (the end of the latest period paid for with money). Each is optional: older servers send none of them, and livemode and paid_through are left out when unknown. Read them from file.license once verifySignedFile() resolves true — they're covered by the signature — and treat a missing one as "not known":

if (await client.verifySignedFile(file, 'your-product-uuid')) {
  const { paid_through, exported_at, livemode } = file.license;
  // e.g. keep the features of builds released by paid_through,
  // and count offline time from exported_at rather than the local clock
}

If you verify the file yourself, verify file.license exactly as received: fields may be added after limits, so an object rebuilt from the fields you know won't verify. verifyKey() returns livemode and paid_through too (not signed there).


5. Check for a Software Update

Safe to call on every launch — same rate-limit tier and license validation as verifyKey(), and no activation required first. Resolves to { update_available: false, release: null } rather than throwing when there's nothing newer.

expectedProductUuid is required, same reasoning as verifyKey() above. The server already scopes which releases you can see to your license's own product_uuid — this is client-side defense-in-depth on top of that, not a substitute for it. productId is optional now (it's still checked as a convenience if you pass it, matched against either your product's original or current display name so a rename never breaks this call), but expectedProductUuid is what your app should actually rely on.

const { update_available, release } = await client.checkUpdate({
  key: 'your-license-key',
  expectedProductUuid: 'your-product-uuid',
  platform: 'darwin-arm64', // must match what was used at release registration
  currentVersion: '2.2.9',
  // productId: 'your-product-id', // optional, defense-in-depth only
  // channel: 'stable', // optional, defaults to 'stable'
});

if (update_available) {
  console.log(`Update available: ${release.version}`);
  console.log(release.artifact_url, release.checksum);
  // release.signature/release.issued_at verify against GET /public-key —
  // see the server's openapi.yaml for the exact signed field order.
}

🧠 Fingerprinting (for License Locking)

import { getFingerprint } from '@casazium/license-sdk/fingerprint';

const fingerprint = getFingerprint(); // consistent per machine

Uses node-machine-id to generate a unique machine identifier.
Safe for Linux, macOS, Windows, containers, and most VMs.


📁 Offline Usage Idea

You can store the following on disk:

{
  "license": { ... },
  "signature": "..."
}

Then verify it at runtime with:

import { CasaziumLicenseClient } from '@casazium/license-sdk';

const client = new CasaziumLicenseClient({ publicKey }); // Required

// true only for an authentic, unexpired license for this product
await client.verifySignedFile({ license, signature }, 'your-product-uuid');

🌐 Browser Usage

import { CasaziumLicenseClient } from '@casazium/license-sdk' resolves to a browser-safe build automatically when bundled with a tool that sets the browser platform/condition (esbuild --platform=browser, webpack, Vite, etc.) — no separate import needed. It has zero Node-only dependencies and no method that takes an admin API key.

If you want to be explicit, or load it directly without a bundler, import the browser entry point by name:

import { CasaziumLicenseClient } from '@casazium/license-sdk/browser';

or drop in the prebuilt global script:

<script src="./node_modules/@casazium/license-sdk/dist/browser.global.js"></script>
<script>
  const client = new CasaziumLicense.CasaziumLicenseClient({
    baseUrl: 'https://your-license-server.com/v1',
  });
</script>

The browser build deliberately omits listLicenses() and revoke(). Both require an admin API key, and a license key embedded in page source is necessarily public — an admin key must never be. Everything else (verifyKey, activate, deactivate, trackUsage, getUsageReport, exportLicense, exportOfflineLicense, getPublicKey, verifySignedFile, checkUpdate) is available and safe to call from the browser with just the license key. If your app needs license management (issuing, listing, revoking), do that from a trusted server context using the Node/CLI client, not from the browser.


🧪 Run Tests

npm install
npm test

🛠 CLI (Optional)

If using the included CLI:

npx casazium-license verify --key YOUR-LICENSE-KEY --expected-product-uuid YOUR-PRODUCT-UUID

--expected-product-uuid is required on verify, verify-file, and check-update — same reasoning as the SDK methods above. Run npx casazium-license --help for the full command list.


📄 License

MIT © 2025 Bob Casazza