Skip to content

licenseseat/licenseseat-rust

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LicenseSeat Rust SDK

Crates.io Tauri Plugin Documentation License: MIT Rust

Official Rust SDK and Tauri plugin for LicenseSeat — the simple, secure licensing platform for desktop apps, games, and plugins.

Table of Contents

Features

  • 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

Packages

This monorepo contains:

Package Description Links
licenseseat Core Rust SDK for any Rust application crates.io docs
tauri-plugin-licenseseat Tauri v2 plugin (Rust side) crates.io
@licenseseat/tauri-plugin Tauri v2 plugin (JS/TS bindings) npm

Quick Start

Tauri Apps

1. Install the Rust plugin and JS bindings:

# Rust side
cargo add tauri-plugin-licenseseat

# JavaScript side
npm add @licenseseat/tauri-plugin

2. 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();

Pure Rust

cargo add licenseseat
use 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(())
}

License Lifecycle

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   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

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 Validation

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

Heartbeat & Seat Tracking

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.

Event System

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

Configuration Reference

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

Examples

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

Documentation

Publishing

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 version

2. 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 main

5. 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 publish

6. Publish to npm:

cd crates/tauri-plugin-licenseseat
npm publish --access public

This 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

Other SDKs

Platform Package Repository
JavaScript/TypeScript @licenseseat/js licenseseat-js
Swift (macOS/iOS) LicenseSeat licenseseat-swift
C# (.NET) LicenseSeat licenseseat-csharp
C++ licenseseat licenseseat-cpp

License

MIT License. See LICENSE for details.