Official Rust SDK and Tauri plugin for LicenseSeat — the simple, secure licensing platform for desktop apps, games, and plugins.
- Features
- Packages
- Quick Start
- License Lifecycle
- Entitlements
- Offline Validation
- Heartbeat & Seat Tracking
- Event System
- Configuration Reference
- Examples
- Documentation
- Publishing
- Other SDKs
- License
- License Lifecycle — Activate, validate, and deactivate licenses with a simple API
- Offline Validation — Machine-file-first Ed25519 + AES-256-GCM offline verification
- Automatic Re-validation — Background validation with configurable intervals
- Heartbeat — Periodic health-check pings for real-time seat tracking
- Entitlement Management — Fine-grained feature access control with expiration support
- Device Telemetry — Auto-collected device metadata (OS, platform, app version)
- Network Resilience — Automatic retry with exponential backoff
- Tauri Integration — First-class Tauri v2 plugin with TypeScript bindings
- High-level State Helpers — Consolidated state snapshots and subscription helpers for app UIs
- Secure by Default — TLS with rustls, no unsafe code
This monorepo contains:
| Package | Description | Links |
|---|---|---|
licenseseat |
Core Rust SDK for any Rust application | |
tauri-plugin-licenseseat |
Tauri v2 plugin (Rust side) | |
@licenseseat/tauri-plugin |
Tauri v2 plugin (JS/TS bindings) |
1. Install the Rust plugin and JS bindings:
# Rust side
cargo add tauri-plugin-licenseseat
# JavaScript side
npm add @licenseseat/tauri-plugin2. Register the plugin in your Tauri app:
// src-tauri/src/main.rs
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_licenseseat::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}3. Add your configuration:
Use your pk_* publishable API key here. Do not embed sk_* secret keys in client apps.
// tauri.conf.json
{
"plugins": {
"licenseseat": {
"apiKey": "pk_live_xxx",
"productSlug": "your-product"
}
}
}4. Add permissions:
// src-tauri/capabilities/default.json
{
"permissions": ["licenseseat:default"]
}5. Use in your frontend:
import {
activate,
deactivate,
getState,
subscribeState,
} from '@licenseseat/tauri-plugin';
// Activate a license
const license = await activate('USER-LICENSE-KEY');
console.log(`Fingerprint: ${license.deviceId}`);
// Read the current state
const state = await getState();
console.log(state.clientStatus, state.planKey);
// Keep UI state in sync
const unlisten = await subscribeState(({ state }) => {
console.log('Updated status:', state.clientStatus);
});
// Deactivate when uninstalling
await deactivate();cargo add licenseseatuse licenseseat::{LicenseSeat, Config};
#[tokio::main]
async fn main() -> licenseseat::Result<()> {
let sdk = LicenseSeat::new(Config::new("pk_live_xxx", "product-slug"));
// Activate a license
let license = sdk.activate("USER-LICENSE-KEY").await?;
println!("Activated! Fingerprint: {}", license.fingerprint());
// Validate the license
let result = sdk.validate().await?;
if result.valid {
println!("License is valid!");
}
// Check entitlements
if sdk.has_entitlement("pro-features") {
println!("Pro features enabled!");
}
// Deactivate when done
sdk.deactivate().await?;
Ok(())
}┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Activate │────▶│ Validate │────▶│ Deactivate │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Heartbeat │ (periodic)
└─────────────┘
| Method | Description |
|---|---|
activate(key) |
Activates a license key on this device. Returns fingerprint/device binding and activation details. |
validate() |
Validates the current license. Returns validity status, entitlements, and warnings. |
deactivate() |
Releases the seat. Call on uninstall or when switching devices. |
heartbeat() |
Sends a health-check ping. Used for real-time seat tracking. |
Entitlements provide fine-grained feature gating. Each entitlement has a key and optional expiration.
// Check if an entitlement is active
if sdk.has_entitlement("cloud-sync") {
enable_cloud_sync();
}
// Get detailed status
let status = sdk.check_entitlement("pro-features");
match status.reason {
None if status.active => println!("Active!"),
Some(EntitlementReason::Expired) => println!("Expired at {:?}", status.expires_at),
Some(EntitlementReason::NotFound) => println!("Not included in plan"),
Some(EntitlementReason::NoLicense) => println!("No active license"),
_ => {}
}
// List entitlements from the cached validation result
if let Some(license) = sdk.current_license() {
if let Some(validation) = license.validation {
for entitlement in validation.license.active_entitlements {
println!("{}: {:?}", entitlement.key, entitlement.expires_at);
}
}
}Offline support is included in the default licenseseat build and mirrors the C++ SDK:
machine files are the preferred offline artifact, with legacy offline tokens available only as an
explicit compatibility fallback.
use licenseseat::{Config, OfflineFallbackMode};
let config = Config {
api_key: "pk_live_xxx".into(),
product_slug: "your-product".into(),
offline_fallback_mode: OfflineFallbackMode::Always,
max_offline_days: 7, // Grace period
enable_legacy_offline_tokens: false,
..Default::default()
};Fallback modes:
| Mode | Description |
|---|---|
NetworkOnly |
Always require network validation (default) |
Always |
Fall back to cached machine files, then legacy offline tokens if explicitly enabled |
Heartbeats enable real-time seat tracking for concurrent user limits:
use std::time::Duration;
let config = Config {
heartbeat_interval: Duration::from_secs(300), // 5 minutes
..Default::default()
};
// Manual heartbeat
let response = sdk.heartbeat().await?;
println!("Server received at: {}", response.received_at);If heartbeats stop (app crash, network loss), the seat is released after the grace period configured in your LicenseSeat dashboard.
Subscribe to SDK events for reactive UI updates:
use licenseseat::{LicenseSeat, EventKind};
let sdk = LicenseSeat::new(config);
let mut events = sdk.subscribe();
tokio::spawn(async move {
while let Ok(event) = events.recv().await {
match event.kind {
EventKind::ActivationSuccess => println!("License activated!"),
EventKind::ValidationFailed => println!("Validation failed"),
EventKind::HeartbeatSuccess => println!("Heartbeat OK"),
EventKind::HeartbeatError => println!("Heartbeat failed"),
_ => {}
}
}
});Available events:
| Event | Description |
|---|---|
ActivationSuccess |
License successfully activated |
ActivationError |
Activation failed |
ValidationSuccess |
License validated successfully |
ValidationFailed |
Validation failed (invalid, expired, etc.) |
DeactivationSuccess |
License deactivated |
DeactivationError |
Deactivation failed |
HeartbeatSuccess |
Heartbeat acknowledged by server |
HeartbeatError |
Heartbeat failed |
| Option | Type | Default | Description |
|---|---|---|---|
api_key |
String |
— | Your publishable LicenseSeat API key (pk_*, required). Keep sk_* server-side only. |
product_slug |
String |
— | Your product slug (required) |
api_base_url |
String |
https://licenseseat.com/api/v1 |
API base URL |
auto_validate_interval |
Duration |
1 hour | Background validation interval |
heartbeat_interval |
Duration |
5 minutes | Heartbeat interval |
offline_fallback_mode |
OfflineFallbackMode |
NetworkOnly |
Offline validation behavior |
offline_token_refresh_interval |
Duration |
72 hours | Offline artifact refresh interval |
enable_legacy_offline_tokens |
bool |
false |
Allow legacy offline-token fallback after machine-file sync fails |
max_offline_days |
u32 |
0 |
Grace period for offline mode |
device_identifier |
Option<String> |
None |
Override the canonical device fingerprint |
signing_public_key |
Option<String> |
None |
Optional pinned public key for offline verification |
telemetry_enabled |
bool |
true |
Send device telemetry |
app_version |
Option<String> |
None |
Your app version (for analytics) |
debug |
bool |
false |
Enable debug logging |
The SDK includes runnable examples:
# Simple heartbeat demo (mimics real app lifecycle)
LICENSESEAT_API_KEY=pk_live_xxx \
LICENSESEAT_PRODUCT_SLUG=your_product \
LICENSESEAT_LICENSE_KEY=your_license \
cargo run --example dev_heartbeat
# Comprehensive stress test (12 scenarios)
cargo run --example stress_test- Core SDK: docs.rs/licenseseat
- Tauri Plugin: docs.rs/tauri-plugin-licenseseat
- Platform Docs: docs.licenseseat.com
- API Reference: docs.licenseseat.com/api
To release a new version:
1. Bump version in Cargo.toml (workspace) and package.json:
# Edit Cargo.toml workspace version
# Edit crates/tauri-plugin-licenseseat/package.json version
# Edit crates/tauri-plugin-licenseseat/Cargo.toml dependency version2. Update CHANGELOG.md and the release notes under docs/releases/:
3. Run release verification:
cargo fmt --all
cargo test -p licenseseat
cargo test -p tauri-plugin-licenseseat
cargo clippy -p licenseseat --tests -- -D warnings
cargo clippy -p tauri-plugin-licenseseat --tests -- -D warnings
cd crates/tauri-plugin-licenseseat
npm ci
npm run build
npm run pack:check
cd ../..
cd crates/licenseseat && cargo publish --dry-run
cd ../tauri-plugin-licenseseat && cargo publish --dry-run
cd ../..4. Commit and push main:
git add -A
git commit -m "Release X.Y.Z"
git push origin main5. Publish to crates.io (order matters):
# Core SDK first
cd crates/licenseseat && cargo publish
# Wait for it to be available, then Tauri plugin
cd ../tauri-plugin-licenseseat && cargo publish6. Publish to npm:
cd crates/tauri-plugin-licenseseat
npm publish --access publicThis requires an npm account with publish access to the @licenseseat scope.
7. Tag the release commit and create the GitHub release:
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.Z
gh release create vX.Y.Z --title "vX.Y.Z" --generate-notes| Platform | Package | Repository |
|---|---|---|
| JavaScript/TypeScript | @licenseseat/js |
licenseseat-js |
| Swift (macOS/iOS) | LicenseSeat |
licenseseat-swift |
| C# (.NET) | LicenseSeat |
licenseseat-csharp |
| C++ | licenseseat |
licenseseat-cpp |
MIT License. See LICENSE for details.