Skip to main content

tauri_plugin_telemetry/
builder.rs

1use std::{panic::PanicHookInfo, sync::Arc};
2
3use tauri::{
4    Manager, RunEvent, Runtime,
5    plugin::{self, TauriPlugin},
6};
7
8use crate::{InitOptions, client::TelemetryClient, commands, config::Config};
9
10/// The telemetry plugin builder.
11pub struct Builder {
12    app_key: String,
13    panic_hook: Option<PanicHook>,
14    options: InitOptions,
15}
16
17pub type PanicHook =
18    Box<dyn Fn(&TelemetryClient, &PanicHookInfo<'_>, String) + 'static + Sync + Send>;
19
20fn get_panic_message(info: &PanicHookInfo) -> String {
21    let payload = info.payload();
22    if let Some(s) = payload.downcast_ref::<&str>() {
23        return s.to_string();
24    } else if let Some(s) = payload.downcast_ref::<String>() {
25        return s.to_string();
26    }
27
28    format!("{:?}", payload)
29}
30
31impl Builder {
32    /// Creates a new builder.
33    pub fn new(app_key: &str) -> Self {
34        Self {
35            app_key: app_key.into(),
36            panic_hook: None,
37            options: Default::default(),
38        }
39    }
40
41    /// Sets custom options to use for the telemetry client.
42    pub fn with_options(mut self, opts: InitOptions) -> Self {
43        self.options = opts;
44        self
45    }
46
47    /// Sets the analytics backend URL. Shorthand for setting
48    /// `InitOptions.host` via [`with_options`](Self::with_options).
49    pub fn with_host(mut self, host: impl Into<String>) -> Self {
50        self.options.host = Some(host.into());
51        self
52    }
53
54    /// Sets a custom panic hook.
55    pub fn with_panic_hook(mut self, hook: PanicHook) -> Self {
56        self.panic_hook = Some(hook);
57        self
58    }
59
60    /// Builds and initializes the plugin.
61    pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
62        plugin::Builder::new("telemetry")
63            .invoke_handler(tauri::generate_handler![commands::track_event])
64            .setup(|app, _api| {
65                let cfg = Config::new(self.app_key, self.options);
66                let app_version = app.package_info().version.to_string();
67                let client = Arc::new(TelemetryClient::new(&cfg, app_version));
68
69                client.start_polling(cfg.flush_interval);
70
71                if let Some(hook) = self.panic_hook {
72                    install_panic_hook(hook, client.clone());
73                }
74
75                app.manage(client);
76                Ok(())
77            })
78            .on_event(move |app, event| {
79                if let RunEvent::Exit = event {
80                    let client = app.state::<Arc<TelemetryClient>>();
81                    client.flush_blocking();
82                }
83            })
84            .build()
85    }
86}
87
88/// Installs a custom panic hook that flushes telemetry events before the
89/// default panic handler runs.
90fn install_panic_hook(hook: PanicHook, client: Arc<TelemetryClient>) {
91    let default_panic = std::panic::take_hook();
92    std::panic::set_hook(Box::new(move |info| {
93        let msg = get_panic_message(info);
94        hook(&client, info, msg);
95        client.flush_blocking();
96        default_panic(info);
97    }));
98}