import { invoke } from "@tauri-apps/api/core"; export type Edition = "free" | "base" | "premium"; export interface LicenseInfo { edition: Edition; email: string; features: string[]; machine_limit: number; issued_at: number; expires_at: number; } export async function validateLicenseKey(key: string): Promise { return invoke("validate_license_key", { key }); } export async function storeLicense(key: string): Promise { return invoke("store_license", { key }); } export async function readLicense(): Promise { return invoke("read_license"); } export async function getEdition(): Promise { return invoke("get_edition"); } export async function getMachineId(): Promise { return invoke("get_machine_id"); } export async function checkEntitlement(feature: string): Promise { return invoke("check_entitlement", { feature }); } export interface MachineInfo { machine_id: string; machine_name: string | null; activated_at: string; last_seen_at: string; } export interface ActivationStatus { is_activated: boolean; machine_id: string; } // ----------------------------------------------------------------------------- // License server errors (issue #270) // ----------------------------------------------------------------------------- // // The network commands (activate_machine, deactivate_machine, // list_activated_machines) reject with a JSON string serialized from the Rust // `LicenseError` enum (license_commands.rs), same pattern as fetch_price: // {"code":"license_revoked"} | {"code":"rate_limit","retry_after_s":42} // | {"code":"server","status":502} | ... // `unknown` is the client-side fallback for anything that is not such a string. export type LicenseErrorCode = | "license_not_found" | "license_revoked" | "license_expired" | "machine_limit_reached" | "no_active_activation" | "invalid_input" | "rate_limit" | "network" | "invalid_response" | "server" | "no_license_stored" | "storage" | "internal" | "unknown"; export interface LicenseError { code: LicenseErrorCode; i18nKey: string; /** Interpolation values for `i18nKey` — only `rate_limit` carries one. */ params?: { seconds: number }; } export const LICENSE_ERROR_I18N_MAP: Record = { license_not_found: "license.errors.licenseNotFound", license_revoked: "license.errors.licenseRevoked", license_expired: "license.errors.licenseExpired", machine_limit_reached: "license.errors.machineLimitReached", no_active_activation: "license.errors.noActiveActivation", invalid_input: "license.errors.invalidInput", rate_limit: "license.errors.rateLimit", network: "license.errors.network", invalid_response: "license.errors.invalidResponse", server: "license.errors.server", no_license_stored: "license.errors.noLicenseStored", storage: "license.errors.storage", internal: "license.errors.internal", unknown: "license.errors.unknown", }; /** Wait shown when a 429 carries no usable retry_after (the server window is 60 s). */ const DEFAULT_RETRY_AFTER_S = 60; function isLicenseErrorCode(code: string): code is LicenseErrorCode { return Object.prototype.hasOwnProperty.call(LICENSE_ERROR_I18N_MAP, code); } /** * Parse the string-serialized Rust `LicenseError` into a typed error with its * i18n key. `invoke` rejects with the `Err(String)` value of the command. */ export function parseLicenseError(e: unknown): LicenseError { if (typeof e === "string") { try { const j = JSON.parse(e) as Record | null; if (j && typeof j.code === "string" && isLicenseErrorCode(j.code)) { const code = j.code; if (code === "rate_limit") { const raw = j.retry_after_s; const seconds = typeof raw === "number" && Number.isFinite(raw) && raw >= 1 ? Math.ceil(raw) : DEFAULT_RETRY_AFTER_S; return { code, i18nKey: LICENSE_ERROR_I18N_MAP.rate_limit, params: { seconds } }; } return { code, i18nKey: LICENSE_ERROR_I18N_MAP[code] }; } } catch { // Not a serialized LicenseError — fall through to `unknown`. } } return { code: "unknown", i18nKey: LICENSE_ERROR_I18N_MAP.unknown }; } export type LicenseResult = { ok: true; value: T } | { ok: false; error: LicenseError }; async function callLicenseCommand( command: string, args?: Record, ): Promise> { try { const value = await invoke(command, args); return { ok: true, value }; } catch (e) { return { ok: false, error: parseLicenseError(e) }; } } export function activateMachine(): Promise> { return callLicenseCommand("activate_machine"); } export function deactivateMachine(machineId: string): Promise> { return callLicenseCommand("deactivate_machine", { machineId }); } export function listActivatedMachines(): Promise> { return callLicenseCommand("list_activated_machines"); } export async function getActivationStatus(): Promise { return invoke("get_activation_status"); }