use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use moonbase::{
DeviceIdFallback, DeviceIdResolver, FileLicenseStore, HttpTransport, Licensing, LicensingOptions,
MoonbaseDeviceIdResolver, MoonbaseDeviceIdResolverOptions,
};
use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
use tauri::webview::PageLoadEvent;
use tauri::{Manager, RunEvent, Runtime, WindowEvent};
mod commands;
mod controller;
mod error;
mod models;
mod ui;
pub use crate::controller::{Moonbase, STATE_CHANGED_EVENT};
pub use crate::error::{Error, Result};
pub use crate::models::{
AddOnsPhase, AddOnsState, DeviceInfo, Failure, LicenseState, LicenseView, LockReason, NotCovered,
PendingActivation, Status, UpdateHint, UpdateInfo,
};
pub use crate::ui::{Presentation, Screen, TrialFeature, UiConfig, UiStrings, WINDOW_LABEL};
pub use moonbase;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub trait MoonbaseExt<R: Runtime> {
fn moonbase(&self) -> &Moonbase<R>;
}
impl<R: Runtime, T: Manager<R>> MoonbaseExt<R> for T {
fn moonbase(&self) -> &Moonbase<R> {
self.state::<Moonbase<R>>().inner()
}
}
#[must_use]
pub struct Builder {
options: LicensingOptions,
license_file: Option<PathBuf>,
device_id_resolver: Option<Arc<dyn DeviceIdResolver>>,
transport: Option<Arc<dyn HttpTransport>>,
allow_device_name_fallback: bool,
open_browser: bool,
ui: UiConfig,
}
impl Builder {
pub fn new(endpoint: impl Into<String>, product_id: impl Into<String>, public_key: impl Into<String>) -> Self {
Self {
options: LicensingOptions::new(endpoint, product_id, public_key),
license_file: None,
device_id_resolver: None,
transport: None,
allow_device_name_fallback: false,
open_browser: true,
ui: UiConfig::default(),
}
}
pub fn account_id(mut self, account_id: impl Into<String>) -> Self {
self.options.account_id = Some(account_id.into());
self
}
pub fn application_version(mut self, version: impl Into<String>) -> Self {
self.options.application_version = Some(version.into());
self
}
pub fn enforce_release_access(mut self, enforce: bool) -> Self {
self.options.enforce_release_access = enforce;
self
}
pub fn auto_activate_sub_products(mut self, auto_activate: bool) -> Self {
self.options.auto_activate_sub_products = auto_activate;
self
}
pub fn license_file(mut self, path: impl Into<PathBuf>) -> Self {
self.license_file = Some(path.into());
self
}
pub fn grace_period(mut self, grace: Duration) -> Self {
self.options.online_validation_grace_period = grace;
self
}
pub fn check_interval(mut self, interval: Duration) -> Self {
self.options.online_validation_min_interval = interval;
self
}
pub fn http_timeouts(mut self, connect: Duration, request: Duration) -> Self {
self.options = self.options.http_timeouts(connect, request);
self
}
pub fn client_info(mut self, client_info: impl Into<String>) -> Self {
self.options.client_info = Some(client_info.into());
self
}
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.options = self.options.metadata(key, value);
self
}
pub fn device_id_resolver(mut self, resolver: impl DeviceIdResolver + 'static) -> Self {
self.device_id_resolver = Some(Arc::new(resolver));
self
}
pub fn transport(mut self, transport: impl HttpTransport + 'static) -> Self {
self.transport = Some(Arc::new(transport));
self
}
pub fn allow_device_name_fallback(mut self, allow: bool) -> Self {
self.allow_device_name_fallback = allow;
self
}
pub fn open_browser(mut self, open: bool) -> Self {
self.open_browser = open;
self
}
pub fn ui(mut self, ui: UiConfig) -> Self {
self.ui = ui;
self
}
pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
let overlay_script = (self.ui.enabled && self.ui.overlay_window().is_some()).then(ui::overlay_script);
let mut plugin = PluginBuilder::new("moonbase");
if let Some(script) = overlay_script {
plugin = plugin.js_init_script(script);
}
plugin
.invoke_handler(tauri::generate_handler![
commands::state,
commands::activate,
commands::cancel_activation,
commands::refresh,
commands::deactivate,
commands::forget,
commands::device_token,
commands::activate_offline,
commands::dismiss_error,
commands::device,
commands::check_update,
commands::update_download_url,
commands::present,
commands::close_ui,
commands::ui_ready,
commands::ui_resize,
commands::save_machine_file,
commands::download_update,
commands::reveal_update,
commands::skip_update,
commands::open_link,
commands::load_add_ons,
commands::refresh_add_ons,
commands::activate_sub_product,
commands::request_sub_product_trial,
commands::buy_sub_product,
commands::upgrade_license,
])
.register_uri_scheme_protocol(ui::SCHEME, |context, request| ui::serve(context.app_handle(), &request))
.setup(move |app, _api| {
let mut options = self.options;
if options.application_version.is_none() {
options.application_version = Some(app.package_info().version.to_string());
}
let segment =
format!("tauri-plugin-moonbase/{VERSION} (Tauri {}; {})", tauri::VERSION, std::env::consts::OS);
options.client_info = Some(match options.client_info.take() {
Some(outer) => format!("{segment} {outer}"),
None => segment,
});
let license_file = match self.license_file {
Some(path) => path,
None => app.path().app_data_dir()?.join("license.mb"),
};
let store = FileLicenseStore::new(license_file);
let resolver: Arc<dyn DeviceIdResolver> = self.device_id_resolver.unwrap_or_else(|| {
Arc::new(MoonbaseDeviceIdResolver::with_options(MoonbaseDeviceIdResolverOptions {
fallback: if self.allow_device_name_fallback {
DeviceIdFallback::DeviceName
} else {
DeviceIdFallback::None
},
..Default::default()
}))
});
let settings = controller::Settings {
grace_period: options.online_validation_grace_period,
check_interval: options.online_validation_min_interval,
application_version: options.application_version.clone(),
open_browser: self.open_browser,
auto_activate_sub_products: options.auto_activate_sub_products,
};
let mut licensing = Licensing::builder(options).store(store.clone()).device_id_resolver(resolver);
if let Some(transport) = self.transport {
licensing = licensing.transport(transport);
}
let licensing = licensing.build()?;
let ui =
self.ui.enabled.then(|| ui::Ui::new(self.ui, licensing.options().endpoint.clone(), store.path()));
let moonbase = Moonbase::new(app.clone(), licensing, store, settings, ui);
moonbase.start();
app.manage(moonbase);
Ok(())
})
.on_page_load(|webview, payload| {
if payload.event() == PageLoadEvent::Finished
&& let Some(moonbase) = webview.try_state::<Moonbase<R>>()
{
moonbase.on_page_load(webview.label());
}
})
.on_event(|app, event| {
let Some(moonbase) = app.try_state::<Moonbase<R>>() else {
return;
};
match event {
RunEvent::Exit => moonbase.shut_down(),
RunEvent::WindowEvent { event: WindowEvent::Focused(focused), .. } => {
moonbase.on_focus_changed(*focused);
}
_ => {}
}
})
.build()
}
}
impl std::fmt::Debug for Builder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Builder")
.field("endpoint", &self.options.endpoint)
.field("product_id", &self.options.product_id)
.field("license_file", &self.license_file)
.finish_non_exhaustive()
}
}