tauri-plugin-telemetry 0.1.3

Backend-agnostic analytics/telemetry plugin for Tauri v2 apps, with a reference Hono + Supabase backend.
Documentation
use std::{panic::PanicHookInfo, sync::Arc};

use tauri::{
    Manager, RunEvent, Runtime,
    plugin::{self, TauriPlugin},
};

use crate::{InitOptions, client::TelemetryClient, commands, config::Config};

/// The telemetry plugin builder.
pub struct Builder {
    app_key: String,
    panic_hook: Option<PanicHook>,
    options: InitOptions,
}

pub type PanicHook =
    Box<dyn Fn(&TelemetryClient, &PanicHookInfo<'_>, String) + 'static + Sync + Send>;

fn get_panic_message(info: &PanicHookInfo) -> String {
    let payload = info.payload();
    if let Some(s) = payload.downcast_ref::<&str>() {
        return s.to_string();
    } else if let Some(s) = payload.downcast_ref::<String>() {
        return s.to_string();
    }

    format!("{:?}", payload)
}

impl Builder {
    /// Creates a new builder.
    pub fn new(app_key: &str) -> Self {
        Self {
            app_key: app_key.into(),
            panic_hook: None,
            options: Default::default(),
        }
    }

    /// Sets custom options to use for the telemetry client.
    pub fn with_options(mut self, opts: InitOptions) -> Self {
        self.options = opts;
        self
    }

    /// Sets the analytics backend URL. Shorthand for setting
    /// `InitOptions.host` via [`with_options`](Self::with_options).
    pub fn with_host(mut self, host: impl Into<String>) -> Self {
        self.options.host = Some(host.into());
        self
    }

    /// Sets a custom panic hook.
    pub fn with_panic_hook(mut self, hook: PanicHook) -> Self {
        self.panic_hook = Some(hook);
        self
    }

    /// Builds and initializes the plugin.
    pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
        plugin::Builder::new("telemetry")
            .invoke_handler(tauri::generate_handler![commands::track_event])
            .setup(|app, _api| {
                let cfg = Config::new(self.app_key, self.options);
                let app_version = app.package_info().version.to_string();
                let client = Arc::new(TelemetryClient::new(&cfg, app_version));

                client.start_polling(cfg.flush_interval);

                if let Some(hook) = self.panic_hook {
                    install_panic_hook(hook, client.clone());
                }

                app.manage(client);
                Ok(())
            })
            .on_event(move |app, event| {
                if let RunEvent::Exit = event {
                    let client = app.state::<Arc<TelemetryClient>>();
                    client.flush_blocking();
                }
            })
            .build()
    }
}

/// Installs a custom panic hook that flushes telemetry events before the
/// default panic handler runs.
fn install_panic_hook(hook: PanicHook, client: Arc<TelemetryClient>) {
    let default_panic = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |info| {
        let msg = get_panic_message(info);
        hook(&client, info, msg);
        client.flush_blocking();
        default_panic(info);
    }));
}