Frontend and permissions
The SDK runs in Rust. Your windows, which show the activation screen and the locked screens, reach it with @licenseman/tauri: typed functions that call the plugin’s commands, and listen to its state event. A capability decides which windows may call what.
Allow the plugin in your capability
{
"identifier": "default",
"windows": ["main"],
"permissions": ["core:default", "opener:default", "licensemanager:default"]
}core:default lets the window listen to events, which watchLicense needs. opener:default comes from Tauri’s opener plugin , to open the customer portal.
Render the license
import { watchLicense } from "@licenseman/tauri";
await watchLicense(({ state, offline, latestAllowedVersion }) => {
if (state === "LOADING") showSplash();
else if (state === "ACTIVE") showApp(offline);
else if (state === "MISSING_KEY") showActivation();
else showLocked(state, latestAllowedVersion);
});Activate the key the customer enters
import { LicenseError, setLicenseKey } from "@licenseman/tauri";
form.addEventListener("submit", async (event) => {
event.preventDefault();
try {
await setLicenseKey(input.value); // watchLicense then shows the app
} catch (error) {
if (error instanceof LicenseError) showError(messages[error.code]);
else throw error;
}
});Errors keep their details across Tauri’s IPC: setLicenseKey rejects with a real LicenseError, with its code, status and latestAllowedVersion. See the errors.
Rendering the license
Each snapshot holds everything a screen needs:
| Field | Type | |
|---|---|---|
state | LicenseState | See license states. |
offline | OfflineState | See offline state. |
latestAllowedVersion | string | null | undefined | In VERSION_NOT_ALLOWED. See license states. |
watchLicense(listener) subscribes to the state event first, then reads the current snapshot, and calls listener with it, then with every change. A window opened after a change, or reloaded, never misses one. It returns a promise of a function that stops listening.
onStateChange(listener) only reports changes: use it when you already read the state some other way. getLicense() reads one snapshot, without listening.
Permissions
licensemanager:default allows everything an activation screen and locked screens need, and nothing more:
| Function | Permission | In default |
|---|---|---|
getState(), ready(), getLicense(), watchLicense() | allow-get-state, allow-ready, allow-get-license | Yes |
setLicenseKey() | allow-set-license-key | Yes |
refresh() | allow-refresh | Yes |
getOfflineState() | allow-get-offline-state | Yes |
getLatestAllowedVersion() | allow-get-latest-allowed-version | Yes |
getPortalUrl() | allow-get-portal-url | Yes |
isClockTampered() | allow-is-clock-tampered | Yes |
getLicenseKeyHint(): the last 5 characters of the key | allow-get-license-key-hint | Yes |
getLicenseKey(): the full key | allow-get-license-key | No |
getFingerprint() | allow-get-fingerprint | No |
Each permission is prefixed with licensemanager:. To give a window less, list the permissions you want instead of licensemanager:default. To give it more, add licensemanager:allow-get-license-key or licensemanager:allow-get-fingerprint. A call without its permission rejects with Tauri’s error, not a LicenseError.
To show the key in your About window, getLicenseKeyHint() is usually enough: “Key ending in B3JDF”.
Gating features
Your webview decides what to show. What must stay locked, like exporting or saving, must be checked in Rust: a determined customer can change what runs in the webview, but not the state the plugin holds in Rust.
use tauri_plugin_licensemanager::{LicenseState, LicensemanagerExt};
#[tauri::command]
fn export_pdf(app: tauri::AppHandle) -> Result<(), String> {
if app.license().state() != LicenseState::Active {
return Err("This feature needs an active license.".into());
}
// …
Ok(())
}See using it from Rust.
Linking to the customer portal
import { getPortalUrl, refresh } from "@licenseman/tauri";
import { openUrl } from "@tauri-apps/plugin-opener";
portalButton.addEventListener("click", async () => openUrl(await getPortalUrl()));
retryButton.addEventListener("click", () => refresh()); // watchLicense shows the resultSee linking to the customer portal.
The plugin’s requests go from Rust: your Content Security Policy doesn’t need to allow api.licensemanager.dev, and you don’t need Tauri’s http plugin.
Edge cases
Several windows each receive every state change: the event is emitted to all of them. Each one needs the permissions in its capability.
Reloading the window, like in development, doesn’t affect the SDK: the state lives in Rust. watchLicense reads it again.
A window opened before the setup is done can’t exist: Tauri creates windows after the plugins’ setup. It can still open in LOADING: watchLicense reports the change when it comes.
Frameworks. The functions work the same with React, Vue, Svelte or plain TypeScript. In React, call watchLicense in an effect, and the function it resolves with in the effect’s cleanup.