#![deny(missing_docs)]
use std::path::PathBuf;
use std::time::Duration;
use tauri::plugin::TauriPlugin;
use tauri::{Manager, RunEvent, Runtime};
mod commands;
mod core;
mod models;
use crate::core::options::{LicenseManagerOptions, TestHooks};
pub use crate::core::{
ConfigError, LatestAllowedVersion, LicenseError, LicenseErrorCode, LicenseManager,
LicenseState, MachineFingerprint, OfflineState, Subscription,
};
pub use crate::models::{LicenseErrorDto, OfflineStateDto, Snapshot};
pub const SDK_NAME: &str = "tauri-plugin-licensemanager";
pub const STATE_CHANGED_EVENT: &str = "licensemanager://state-changed";
const MOBILE_ERROR: &str =
"tauri-plugin-licensemanager supports desktop only (macOS, Windows, Linux)";
#[derive(Debug)]
#[must_use = "call `.build()` and pass the result to `tauri::Builder::plugin`"]
pub struct Builder {
product_id: String,
public_key: String,
fingerprint: Option<MachineFingerprint>,
version: Option<String>,
machine_name: Option<String>,
platform: Option<String>,
min_offline_duration: Duration,
api_base_url: Option<String>,
storage_dirs: Option<Vec<PathBuf>>,
testing: Option<TestHooks>,
}
impl Builder {
pub fn new(product_id: impl Into<String>, public_key: impl Into<String>) -> Self {
Self {
product_id: product_id.into(),
public_key: public_key.into(),
fingerprint: None,
version: None,
machine_name: None,
platform: None,
min_offline_duration: Duration::ZERO,
api_base_url: None,
storage_dirs: None,
testing: None,
}
}
pub fn fingerprint(mut self, fingerprint: impl Into<MachineFingerprint>) -> Self {
self.fingerprint = Some(fingerprint.into());
self
}
pub fn version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
pub fn machine_name(mut self, machine_name: impl Into<String>) -> Self {
self.machine_name = Some(machine_name.into());
self
}
pub fn platform(mut self, platform: impl Into<String>) -> Self {
self.platform = Some(platform.into());
self
}
pub fn min_offline_duration(mut self, duration: Duration) -> Self {
self.min_offline_duration = duration;
self
}
pub fn api_base_url(mut self, url: impl Into<String>) -> Self {
self.api_base_url = Some(url.into());
self
}
#[cfg(feature = "test-util")]
#[doc(hidden)]
pub fn storage_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
self.storage_dirs = Some(dirs);
self
}
#[cfg(feature = "test-util")]
#[doc(hidden)]
pub fn testing(mut self, hooks: testing::TestHooks) -> Self {
self.testing = Some(hooks);
self
}
#[cfg(test)]
fn test_hooks(mut self, dirs: Vec<PathBuf>, hooks: TestHooks) -> Self {
self.storage_dirs = Some(dirs);
self.testing = Some(hooks);
self
}
fn into_options(
self,
default_version: String,
runtime: tokio::runtime::Handle,
) -> LicenseManagerOptions {
LicenseManagerOptions {
product_id: self.product_id,
public_key: self.public_key,
fingerprint: self.fingerprint,
version: self.version.unwrap_or(default_version),
machine_name: self.machine_name,
platform: self.platform,
min_offline_duration: self.min_offline_duration,
api_base_url: self.api_base_url,
storage_dirs: self.storage_dirs,
runtime,
testing: self.testing,
}
}
pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
tauri::plugin::Builder::new("licensemanager")
.invoke_handler(tauri::generate_handler![
commands::get_state,
commands::ready,
commands::get_license,
commands::set_license_key,
commands::refresh,
commands::get_offline_state,
commands::get_latest_allowed_version,
commands::get_portal_url,
commands::is_clock_tampered,
commands::get_license_key_hint,
commands::get_license_key,
commands::get_fingerprint,
])
.setup(move |app, _api| {
setup(app, self)?;
Ok(())
})
.on_event(|app, event| {
if let RunEvent::Exit = event {
if let Some(manager) = app.try_state::<LicenseManager>() {
manager.dispose();
}
}
})
.build()
}
}
struct EventForwarding {
_subscription: Subscription,
}
#[cfg(mobile)]
fn setup<R: Runtime>(_app: &tauri::AppHandle<R>, _builder: Builder) -> Result<(), String> {
Err(MOBILE_ERROR.into())
}
#[cfg(not(mobile))]
fn setup<R: Runtime>(app: &tauri::AppHandle<R>, builder: Builder) -> Result<(), String> {
use tauri::Emitter;
let _ = MOBILE_ERROR;
let options = builder.into_options(
app.package_info().version.to_string(),
tauri::async_runtime::handle().inner().clone(),
);
let manager = LicenseManager::new(options).map_err(|e| e.to_string())?;
let handle = app.clone();
let subscription = manager.on_state_change(move |state| {
if let Some(manager) = handle.try_state::<LicenseManager>() {
let snapshot = Snapshot {
state,
..Snapshot::of(&manager)
};
let _ = handle.emit(STATE_CHANGED_EVENT, snapshot);
}
});
app.manage(manager);
app.manage(EventForwarding {
_subscription: subscription,
});
Ok(())
}
pub trait LicensemanagerExt<R: Runtime> {
fn license(&self) -> &LicenseManager;
}
impl<R: Runtime, T: Manager<R>> LicensemanagerExt<R> for T {
fn license(&self) -> &LicenseManager {
self.state::<LicenseManager>().inner()
}
}
#[cfg(feature = "test-util")]
#[doc(hidden)]
#[allow(missing_docs)]
pub mod testing {
use std::sync::Arc;
pub use crate::core::api::{post_validate_key, ValidateKeyRequest};
pub use crate::core::clock::Clock;
pub use crate::core::options::TestHooks;
pub use crate::core::store::{SecretBackend, SecretBackendError, SecureStore};
pub use crate::core::test_support::*;
pub use crate::core::transport::{HttpResponse, Transport, TransportError};
use crate::{Builder, ConfigError, LicenseManager};
pub fn manager(builder: Builder, default_version: &str) -> Result<LicenseManager, ConfigError> {
LicenseManager::new(
builder.into_options(default_version.into(), tokio::runtime::Handle::current()),
)
}
pub fn reqwest_transport() -> Arc<dyn Transport> {
Arc::new(crate::core::transport::ReqwestTransport::new())
}
pub fn os_keyring() -> Option<Arc<dyn SecretBackend>> {
crate::core::store::default_backend()
}
}
#[cfg(test)]
mod tests;