Rust SDK
Official Rust SDK and Tauri plugin for LicenseSeat. Use the core licenseseat crate in native Rust applications, or pair tauri-plugin-licenseseat with @licenseseat/tauri-plugin in Tauri v2 apps.
Current stable release: v0.6.0. The matching core crate, Tauri crate, and frontend bindings are all version 0.6.0.
Packages
This SDK family ships as three published packages:
| Package | Description | Links |
|---|---|---|
licenseseat |
Core Rust SDK for native Rust apps | crates.io 0.6.0 |
tauri-plugin-licenseseat |
Rust-side Tauri v2 plugin | crates.io 0.6.0 |
@licenseseat/tauri-plugin |
TypeScript/JavaScript bindings for the Tauri plugin | npm 0.6.0 |
Features
- Full license lifecycle: activate, validate, deactivate, heartbeat
- Machine-file-first offline validation with Ed25519 signatures and AES-256-GCM payload encryption
- Durable random installation identity by default, with opt-in C++-compatible hardware fingerprint components
- Entitlement checks and cached validation state
- Background re-validation, heartbeat, and network recheck loops
- Restore cached license state on app startup
- Release discovery and signed download-token APIs
- Tauri v2 plugin with typed JS/TS bindings, state snapshots, and event subscriptions
- Manual offline helpers for legacy offline tokens and machine files
Installation
Pure Rust
cargo add [email protected]
Offline validation support is included in the default feature set. If you disable default features and still want offline verification, add it back explicitly:
cargo add [email protected] --no-default-features --features "native-tls,offline"
Tauri v2
Install the Rust plugin in src-tauri/:
cd src-tauri
cargo add [email protected]
Install the frontend bindings in your app:
npm add @licenseseat/[email protected]
For applications, review and commit Cargo.lock and the frontend package-manager
lockfile so the complete dependency graph—not only these direct requirements—is
reproducible in production.
Quick Start
Pure Rust
use licenseseat::{Config, LicenseSeat};
#[tokio::main]
async fn main() -> licenseseat::Result<()> {
let sdk = LicenseSeat::try_new(Config::new("pk_live_xxxxxxxx", "your-product"))?;
sdk.activate("USER-LICENSE-KEY").await?;
println!("License activated");
let validation = sdk.validate().await?;
if validation.valid {
println!("Plan: {}", validation.license.plan_key);
}
if sdk.has_entitlement("pro-features") {
enable_pro_features();
}
Ok(())
}
Tauri v2
Register the plugin:
// 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");
}
Configure it in tauri.conf.json:
{
"plugins": {
"licenseseat": {
"apiKey": "pk_live_xxxxxxxx",
"productSlug": "your-product"
}
}
}
Add permissions:
{
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"licenseseat:default"
]
}
Use it in the frontend:
import {
activate,
bootstrapState,
hasAnyEntitlement,
subscribeState,
} from '@licenseseat/tauri-plugin';
const state = await bootstrapState();
if (!state.license) {
await activate('USER-LICENSE-KEY');
}
if (await hasAnyEntitlement(['pro-features', 'cloud-sync'])) {
enableProFeatures();
}
const unlisten = await subscribeState(({ state: nextState }) => {
console.log('Client status:', nextState.clientStatus);
}, { emitCurrent: true });
Configuration
Rust Configuration
use licenseseat::{Config, OfflineFallbackMode};
use std::time::Duration;
let config = Config {
api_key: "pk_live_xxxxxxxx".into(),
product_slug: "your-product".into(),
api_base_url: "https://licenseseat.com/api/v1".into(),
storage_prefix: "licenseseat_".into(),
storage_path: None,
device_identifier: None,
signing_public_key: None,
signing_key_id: None,
auto_validate_interval: Duration::from_secs(3600),
heartbeat_interval: Duration::from_secs(300),
network_recheck_interval: Duration::from_secs(30),
request_timeout: Duration::from_secs(30),
verify_ssl: true,
max_retries: 3,
retry_delay: Duration::from_secs(1),
offline_fallback_mode: OfflineFallbackMode::NetworkOnly,
offline_token_refresh_interval: Duration::from_secs(259_200),
enable_legacy_offline_tokens: false,
max_offline_days: 0,
max_clock_skew: Duration::from_secs(300),
telemetry_enabled: true,
app_version: Some("1.0.0".into()),
app_build: Some("100".into()),
debug: false,
..Default::default()
};
Tauri Plugin Configuration
{
"plugins": {
"licenseseat": {
"apiKey": "pk_live_xxxxxxxx",
"productSlug": "your-product",
"apiBaseUrl": "https://licenseseat.com/api/v1",
"storagePrefix": "licenseseat_",
"storagePath": null,
"deviceIdentifier": null,
"signingPublicKey": "BASE64_ED25519_PUBLIC_KEY",
"signingKeyId": "production-key-v1",
"autoValidateInterval": 3600,
"heartbeatInterval": 300,
"networkRecheckInterval": 30,
"timeoutSeconds": 30,
"verifySsl": true,
"offlineFallbackMode": "networkOnly",
"offlineTokenRefreshInterval": 259200,
"enableLegacyOfflineTokens": false,
"maxOfflineDays": 7,
"telemetryEnabled": true,
"appVersion": "1.0.0",
"appBuild": "100",
"debug": false
}
}
}
Key Options
| Rust | Tauri | Description |
|---|---|---|
api_key |
apiKey |
Publishable API key |
product_slug |
productSlug |
Product slug |
api_base_url |
apiBaseUrl |
API base URL |
storage_prefix |
storagePrefix |
Cache namespace prefix |
storage_path |
storagePath |
Optional persisted cache directory |
device_identifier |
deviceIdentifier |
Override the default durable random installation identity |
send_fingerprint_components |
sendFingerprintComponents |
Opt in to raw hardware fingerprint component collection (default false) |
signing_public_key |
signingPublicKey |
Optional pinned Ed25519 key for offline verification |
signing_key_id |
signingKeyId |
Key ID associated with signing_public_key |
auto_validate_interval |
autoValidateInterval |
Background validation interval |
heartbeat_interval |
heartbeatInterval |
Background heartbeat interval |
network_recheck_interval |
networkRecheckInterval |
Connectivity recheck interval while offline |
request_timeout |
timeoutSeconds |
HTTP request timeout |
max_retries |
maxRetries |
Maximum retry attempts for retryable requests (default 3) |
retry_delay_seconds |
retryDelaySeconds |
Initial retry delay in seconds; exponential backoff is capped by the SDK (default 1) |
verify_ssl |
verifySsl |
TLS certificate verification |
offline_fallback_mode |
offlineFallbackMode |
Offline fallback strategy |
offline_token_refresh_interval |
offlineTokenRefreshInterval |
Periodic refresh interval for cached offline artifacts. Zero disables periodic refresh, not the one-shot sync after activation. |
enable_legacy_offline_tokens |
enableLegacyOfflineTokens |
Allow legacy offline-token fallback |
max_clock_skew_seconds |
maxClockSkewSeconds |
Maximum accepted clock skew for signed offline artifacts, in seconds (default 300) |
max_offline_days |
maxOfflineDays |
Optional host-side signed-artifact age cap. 0 means no additional cap; signed expiry is still enforced. |
telemetry_enabled |
telemetryEnabled |
Telemetry toggle |
app_version |
appVersion |
App version for telemetry |
app_build |
appBuild |
App build for telemetry |
debug |
debug |
Debug logging |
emit_frontend_events |
emitFrontendEvents |
Emit SDK events to the webview (default true). Set false when the app exposes a narrow native licensing facade; native Rust subscribers still receive events |
Offline Validation
The Rust SDK now follows the same machine-file-first offline flow as the reference C++ SDK.
Fallback Modes
| Rust | Tauri | Behavior |
|---|---|---|
OfflineFallbackMode::NetworkOnly |
omit the option or use networkOnly |
Default. May consult a signed offline artifact after a transport failure, timeout, HTTP 408, or 5xx response. |
OfflineFallbackMode::Always |
always |
Includes every NetworkOnly case and HTTP 429 rate limiting—nothing else. |
Neither mode overrides authentication or configuration failures, ordinary
client/business errors, malformed or identity-substituted responses, or
superseded local operations. networkOnly is the canonical default;
network_only is also accepted. The legacy aliases allow_offline,
allowOffline, offline_first, and offlineFirst remain accepted only for
compatibility and map to always.
Production offline startup requires both a pinned signing_public_key /
signingPublicKey and its matching signing_key_id / signingKeyId. A key
fetched online may establish trust for the current process; its persisted copy
is diagnostic cache and is deliberately not a trust anchor after restart.
How It Works
- Activation binds the license to a canonical fingerprint.
- The SDK starts one background machine-file checkout after activation.
- The machine file is Ed25519-signed and AES-256-GCM encrypted.
- When offline, the SDK verifies the signature, decrypts the payload, checks the fingerprint binding, and enforces expiry/grace rules locally.
- Legacy offline tokens remain available only as an explicit compatibility fallback via
enable_legacy_offline_tokens.
Activation already schedules the initial offline-asset sync. Do not normally
call sync_offline_assets() / syncOfflineAssets() immediately afterward:
that starts a second, serialized checkout. Use the manual method later when an
explicit refresh or retry is intentional. Applications that need to confirm
initial readiness should subscribe to the machine-file/offline-asset events
before activation.
Manual Offline Helpers
Pure Rust:
let machine_file = sdk.checkout_machine_file("USER-LICENSE-KEY", None, Some(30)).await?;
let verification = sdk.verify_machine_file(&machine_file, None, None, None)?;
if verification.valid {
println!("Offline machine file verified");
}
Tauri frontend:
import {
checkoutMachineFile,
verifyMachineFile,
fetchSigningKey,
} from '@licenseseat/tauri-plugin';
const machineFile = await checkoutMachineFile('USER-LICENSE-KEY');
await fetchSigningKey('offline-key-1');
const result = await verifyMachineFile(machineFile);
if (result.valid) {
enableOfflineMode();
}
Legacy offline-token helpers remain available for manual workflows:
- Rust:
generate_offline_token(),verify_offline_token() - Tauri:
generateOfflineToken(),verifyOfflineToken()
For a later intentional refresh or retry while the app is online, the Tauri
plugin exposes syncOfflineAssets(). The initial post-activation sync is
automatic, so an immediate manual call is redundant.
License Lifecycle
Rust
let license = sdk.activate("USER-LICENSE-KEY").await?;
let validation = sdk.validate().await?;
if validation.valid {
println!("License valid");
}
let heartbeat = sdk.heartbeat().await?;
println!("Heartbeat acknowledged at {}", heartbeat.received_at);
sdk.deactivate().await?;
Tauri
import {
activate,
validate,
heartbeat,
deactivate,
getClientStatus,
} from '@licenseseat/tauri-plugin';
await activate('USER-LICENSE-KEY');
const validation = await validate();
if (validation.valid) {
console.log('License valid');
}
await heartbeat();
console.log(await getClientStatus()); // active, offline_valid, inactive, invalid, pending, offline_invalid
await deactivate();
Recommended Tauri Integration
For most Tauri apps, treat the plugin as a stateful client rather than wiring every lifecycle event manually:
- Use
bootstrapState()on startup to restore cached state and optionally run one validation pass. - Use
activateAndGetState()after a successful activation flow when your UI needs the refreshed state immediately. - Use
getState()for one-off reads. - Use
subscribeState()for UI updates driven by lifecycle changes. - Use
normalizeError()before rendering frontend-facing errors.
import {
activateAndGetState,
bootstrapState,
normalizeError,
subscribeState,
} from '@licenseseat/tauri-plugin';
const initialState = await bootstrapState();
const unlisten = await subscribeState(({ state }) => {
renderLicenseState(state);
}, { emitCurrent: true });
try {
const nextState = await activateAndGetState('USER-LICENSE-KEY');
renderLicenseState(nextState);
} catch (error) {
const licenseError = normalizeError(error);
showError(licenseError.message);
}
Restore, Releases, and Distribution
The Rust and Tauri SDKs now expose the same restore and release-management APIs as the C++ SDK.
Restore Cached State
Rust:
let restored = sdk.restore_license().await;
if restored.restored {
println!("Restored cached session");
}
Tauri:
import { restoreLicense } from '@licenseseat/tauri-plugin';
const restored = await restoreLicense();
if (restored.restored) {
console.log(restored.status.status);
}
Releases and Download Tokens
Rust:
let release = sdk.get_latest_release(None, None, None).await?;
let token = sdk
.generate_download_token(&release.version, "USER-LICENSE-KEY", None, None)
.await?;
Tauri:
import { getLatestRelease, generateDownloadToken } from '@licenseseat/tauri-plugin';
const release = await getLatestRelease();
const token = await generateDownloadToken(release.version, 'USER-LICENSE-KEY');
Tauri API Surface
Core Commands
| Function | Description |
|---|---|
activate(key, options?) |
Activate a license key |
validateKey(key) |
Validate an explicit license key |
validate() |
Validate the current cached license |
deactivate() |
Deactivate the current license |
deactivateKey(key, fingerprint?) |
Deactivate an explicit license/fingerprint pair |
heartbeat() |
Send a heartbeat for the current license |
heartbeatKey(key, fingerprint?) |
Heartbeat an explicit license/fingerprint pair |
getStatus() |
Get the structured status object |
getClientStatus() |
Get the stable status string |
isOnline() |
Check the SDK's current online/offline view |
getFingerprint() |
Read the current fingerprint |
restoreLicense() |
Restore cached license state |
health() |
Check API reachability |
reset() |
Clear cached SDK state |
State, Events, and UI Helpers
| Function | Description |
|---|---|
getState() |
Get a consolidated SDK state snapshot |
getAdminSnapshot() |
Get a detailed admin/debug snapshot |
restoreAndGetState() |
Restore a cached session and return the refreshed state |
activateAndGetState(key, options?) |
Activate, attempt validation, and return the latest state |
bootstrapState(options?) |
Restore, optionally validate, and return the latest state |
listenEvent(name, handler) |
Subscribe to a specific stable plugin event |
subscribeState(listener, options?) |
Subscribe to state-changing lifecycle events |
normalizeError(error) |
Normalize invoke/plugin errors into LicenseSeatPluginError |
Entitlements and Cached State
| Function | Description |
|---|---|
checkEntitlement(key) |
Get detailed entitlement status |
hasEntitlement(key) |
Boolean entitlement check |
hasAnyEntitlement(keys) |
True when any provided entitlement is active |
hasAllEntitlements(keys) |
True when all provided entitlements are active |
getEntitlements() |
List active entitlements from cached validation |
getLicense() |
Get the cached license, if any |
getActiveEntitlementKeys() |
List active entitlement keys from the current state snapshot |
getPlanKey() |
Get the current plan key from the state snapshot |
getLicenseMode() |
Get the current license mode from the state snapshot |
Releases and Distribution
| Function | Description |
|---|---|
getLatestRelease(productSlug?, channel?, platform?) |
Fetch the latest published release |
listReleases(productSlug?, options?) |
List releases with pagination metadata |
generateDownloadToken(version, licenseKey, productSlug?, platform?) |
Create a signed release download token |
Offline Helpers
| Function | Description |
|---|---|
generateOfflineToken(key, fingerprint?, ttlDays?) |
Generate a legacy offline token |
verifyOfflineToken(token, publicKeyB64?) |
Verify a legacy offline token locally |
checkoutMachineFile(key, options?) |
Checkout a machine file |
fetchSigningKey(keyId) |
Fetch and cache an Ed25519 public key |
syncOfflineAssets() |
Refresh cached machine-file/signing-key/token artifacts |
verifyMachineFile(file, options?) |
Verify and decrypt a machine file locally |
Entitlements
Rust:
if sdk.has_entitlement("cloud-sync") {
enable_cloud_sync();
}
let status = sdk.check_entitlement("pro-features");
if status.active {
enable_pro_features();
}
Tauri:
import { hasEntitlement, checkEntitlement, getEntitlements } from '@licenseseat/tauri-plugin';
if (await hasEntitlement('cloud-sync')) {
enableCloudSync();
}
const status = await checkEntitlement('pro-features');
const entitlements = await getEntitlements();
Events
Both the Rust crate and the Tauri plugin expose license lifecycle events.
Rust
let mut events = sdk.subscribe();
tokio::spawn(async move {
while let Ok(event) = events.recv().await {
println!("event = {}", event.kind);
}
});
Tauri
import {
LICENSESEAT_EVENTS,
listenEvent,
subscribeState,
} from '@licenseseat/tauri-plugin';
await listenEvent(LICENSESEAT_EVENTS.VALIDATION_SUCCESS, () => {
console.log('License validation succeeded');
});
const unlisten = await subscribeState(({ state, eventName }) => {
console.log('State changed via', eventName, state.clientStatus);
}, { emitCurrent: true });
Common event families:
- Activation, validation, deactivation, and heartbeat success/error events
- Online/offline network state events
- Offline validation success/failure events
- Machine-file fetch, ready, verified, and verification-failed events
- Legacy offline-token events for compatibility flows
Status Values
The stable client-status values are:
activeoffline_validoffline_invalidinactiveinvalidpending
Tauri's getClientStatus() and getStatus() use these snake_case values.
Tauri Permissions
The plugin integrates with Tauri's permissions system. licenseseat:default
is intentionally limited to ordinary lifecycle, status, and entitlement
operations; it does not grant every diagnostic, offline-management, release,
or destructive command.
| Permission set | Additional surface |
|---|---|
licenseseat:diagnostics |
Detailed admin snapshots, including cached-artifact and local-path diagnostics |
licenseseat:advanced-lifecycle |
Explicit key/fingerprint operations and destructive reset |
licenseseat:offline-management |
Raw artifact checkout/verification, signing-key lookup, and refresh |
licenseseat:releases |
Release lookup and license-bound download-token generation |
For fine-grained access, use the generated per-command permission identifiers, for example:
licenseseat:allow-get-statelicenseseat:allow-get-admin-snapshotlicenseseat:allow-sync-offline-assetslicenseseat:allow-verify-machine-file
These command-specific identifiers are generated from the plugin command surface and shipped with the crate under permissions/autogenerated/commands/.
Do not load untrusted remote content into a Tauri window that has LicenseSeat permissions. The generic renderer API necessarily handles customer-entered license keys and returns licensing state. Higher-assurance applications should keep those permissions out of the renderer, manage the core SDK in Rust, and expose a narrow app-specific command facade with redacted state and native entitlement gates.
Platform Requirements
| Platform | Requirement |
|---|---|
| Rust | 1.85+ for the core crate; 1.88+ for the Tauri plugin |
| Tauri | v2.0.0+ |
| Node.js | 18+ for the JS bindings |
| TLS | rustls by default, native-tls optional in the core crate |
Security Notes
- Use only a restricted
pk_*publishable key in client applications. Never ship ansk_*, administrator, or server-write credential, and assume every embedded client key can be extracted. - Keep license keys out of URLs, logs, crash reports, analytics, renderer events, and untrusted IPC responses.
- Machine files are signed, encrypted, activation-bound, and fingerprint-bound.
- The SDK caches signing keys by
kidand can also use a pinned public key. - As of 0.6.0, the default installation identity is a durable random UUID, scoped to the app/product and persisted in application data; existing legacy identifiers are adopted where safe. It is stable, used for seat binding and offline validation, and is not derived from hardware.
- Raw hardware fingerprint components (platform machine identifiers and a hostname fallback) are collected and sent only with the explicit
send_fingerprint_components = trueopt-in, or when a caller supplies a component map for a single machine-file checkout. Hardware-derived identity remains available through these opt-ins for legacy interoperability. Treat any collected components as device data rather than anonymous data in the host application's privacy disclosures.