tauri_plugin_licensemanager/
lib.rs1#![deny(missing_docs)]
2use std::path::PathBuf;
41use std::time::Duration;
42
43use tauri::plugin::TauriPlugin;
44use tauri::{Manager, RunEvent, Runtime};
45
46mod commands;
47mod core;
48mod models;
49
50use crate::core::options::{LicenseManagerOptions, TestHooks};
51pub use crate::core::{
52 ConfigError, LatestAllowedVersion, LicenseError, LicenseErrorCode, LicenseManager,
53 LicenseState, MachineFingerprint, OfflineState, Subscription,
54};
55pub use crate::models::{LicenseErrorDto, OfflineStateDto, Snapshot};
56
57pub const SDK_NAME: &str = "tauri-plugin-licensemanager";
59
60pub const STATE_CHANGED_EVENT: &str = "licensemanager://state-changed";
62
63const MOBILE_ERROR: &str =
64 "tauri-plugin-licensemanager supports desktop only (macOS, Windows, Linux)";
65
66#[derive(Debug)]
70#[must_use = "call `.build()` and pass the result to `tauri::Builder::plugin`"]
71pub struct Builder {
72 product_id: String,
73 public_key: String,
74 fingerprint: Option<MachineFingerprint>,
75 version: Option<String>,
76 machine_name: Option<String>,
77 platform: Option<String>,
78 min_offline_duration: Duration,
79 api_base_url: Option<String>,
80 storage_dirs: Option<Vec<PathBuf>>,
81 testing: Option<TestHooks>,
82}
83
84impl Builder {
85 pub fn new(product_id: impl Into<String>, public_key: impl Into<String>) -> Self {
88 Self {
89 product_id: product_id.into(),
90 public_key: public_key.into(),
91 fingerprint: None,
92 version: None,
93 machine_name: None,
94 platform: None,
95 min_offline_duration: Duration::ZERO,
96 api_base_url: None,
97 storage_dirs: None,
98 testing: None,
99 }
100 }
101
102 pub fn fingerprint(mut self, fingerprint: impl Into<MachineFingerprint>) -> Self {
106 self.fingerprint = Some(fingerprint.into());
107 self
108 }
109
110 pub fn version(mut self, version: impl Into<String>) -> Self {
114 self.version = Some(version.into());
115 self
116 }
117
118 pub fn machine_name(mut self, machine_name: impl Into<String>) -> Self {
120 self.machine_name = Some(machine_name.into());
121 self
122 }
123
124 pub fn platform(mut self, platform: impl Into<String>) -> Self {
127 self.platform = Some(platform.into());
128 self
129 }
130
131 pub fn min_offline_duration(mut self, duration: Duration) -> Self {
136 self.min_offline_duration = duration;
137 self
138 }
139
140 pub fn api_base_url(mut self, url: impl Into<String>) -> Self {
142 self.api_base_url = Some(url.into());
143 self
144 }
145
146 #[cfg(feature = "test-util")]
148 #[doc(hidden)]
149 pub fn storage_dirs(mut self, dirs: Vec<PathBuf>) -> Self {
150 self.storage_dirs = Some(dirs);
151 self
152 }
153
154 #[cfg(feature = "test-util")]
156 #[doc(hidden)]
157 pub fn testing(mut self, hooks: testing::TestHooks) -> Self {
158 self.testing = Some(hooks);
159 self
160 }
161
162 #[cfg(test)]
163 fn test_hooks(mut self, dirs: Vec<PathBuf>, hooks: TestHooks) -> Self {
164 self.storage_dirs = Some(dirs);
165 self.testing = Some(hooks);
166 self
167 }
168
169 fn into_options(
170 self,
171 default_version: String,
172 runtime: tokio::runtime::Handle,
173 ) -> LicenseManagerOptions {
174 LicenseManagerOptions {
175 product_id: self.product_id,
176 public_key: self.public_key,
177 fingerprint: self.fingerprint,
178 version: self.version.unwrap_or(default_version),
179 machine_name: self.machine_name,
180 platform: self.platform,
181 min_offline_duration: self.min_offline_duration,
182 api_base_url: self.api_base_url,
183 storage_dirs: self.storage_dirs,
184 runtime,
185 testing: self.testing,
186 }
187 }
188
189 pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
191 tauri::plugin::Builder::new("licensemanager")
192 .invoke_handler(tauri::generate_handler![
193 commands::get_state,
194 commands::ready,
195 commands::get_license,
196 commands::set_license_key,
197 commands::refresh,
198 commands::get_offline_state,
199 commands::get_latest_allowed_version,
200 commands::get_portal_url,
201 commands::is_clock_tampered,
202 commands::get_license_key_hint,
203 commands::get_license_key,
204 commands::get_fingerprint,
205 ])
206 .setup(move |app, _api| {
207 setup(app, self)?;
208 Ok(())
209 })
210 .on_event(|app, event| {
211 if let RunEvent::Exit = event {
212 if let Some(manager) = app.try_state::<LicenseManager>() {
213 manager.dispose();
214 }
215 }
216 })
217 .build()
218 }
219}
220
221struct EventForwarding {
223 _subscription: Subscription,
224}
225
226#[cfg(mobile)]
227fn setup<R: Runtime>(_app: &tauri::AppHandle<R>, _builder: Builder) -> Result<(), String> {
228 Err(MOBILE_ERROR.into())
229}
230
231#[cfg(not(mobile))]
232fn setup<R: Runtime>(app: &tauri::AppHandle<R>, builder: Builder) -> Result<(), String> {
233 use tauri::Emitter;
234
235 let _ = MOBILE_ERROR;
236 let options = builder.into_options(
237 app.package_info().version.to_string(),
238 tauri::async_runtime::handle().inner().clone(),
239 );
240 let manager = LicenseManager::new(options).map_err(|e| e.to_string())?;
241 let handle = app.clone();
242 let subscription = manager.on_state_change(move |state| {
243 if let Some(manager) = handle.try_state::<LicenseManager>() {
244 let snapshot = Snapshot {
245 state,
246 ..Snapshot::of(&manager)
247 };
248 let _ = handle.emit(STATE_CHANGED_EVENT, snapshot);
249 }
250 });
251 app.manage(manager);
252 app.manage(EventForwarding {
253 _subscription: subscription,
254 });
255 Ok(())
256}
257
258pub trait LicensemanagerExt<R: Runtime> {
260 fn license(&self) -> &LicenseManager;
262}
263
264impl<R: Runtime, T: Manager<R>> LicensemanagerExt<R> for T {
265 fn license(&self) -> &LicenseManager {
266 self.state::<LicenseManager>().inner()
267 }
268}
269
270#[cfg(feature = "test-util")]
272#[doc(hidden)]
273#[allow(missing_docs)]
274pub mod testing {
275 use std::sync::Arc;
276
277 pub use crate::core::api::{post_validate_key, ValidateKeyRequest};
278 pub use crate::core::clock::Clock;
279 pub use crate::core::options::TestHooks;
280 pub use crate::core::store::{SecretBackend, SecretBackendError, SecureStore};
281 pub use crate::core::test_support::*;
282 pub use crate::core::transport::{HttpResponse, Transport, TransportError};
283 use crate::{Builder, ConfigError, LicenseManager};
284
285 pub fn manager(builder: Builder, default_version: &str) -> Result<LicenseManager, ConfigError> {
287 LicenseManager::new(
288 builder.into_options(default_version.into(), tokio::runtime::Handle::current()),
289 )
290 }
291
292 pub fn reqwest_transport() -> Arc<dyn Transport> {
294 Arc::new(crate::core::transport::ReqwestTransport::new())
295 }
296
297 pub fn os_keyring() -> Option<Arc<dyn SecretBackend>> {
299 crate::core::store::default_backend()
300 }
301}
302
303#[cfg(test)]
304mod tests;