tauri_plugin_better_posthog/lib.rs
1//! Tauri integration with PostHog.
2//!
3//! This plugin wraps the [`better_posthog`] crate to provide seamless analytics integration for Tauri applications.
4//! It automatically handles:
5//!
6//! - User identity management with configurable strategies
7//! - Session tracking across the application lifecycle
8//! - Tauri-specific context injection (app name, version, webview info)
9//! - Both Rust backend and frontend JavaScript event capture
10//!
11//! # Prerequisites
12//!
13//! The consuming application must initialize the `better_posthog` global client **before** registering the plugin:
14//!
15//! ```ignore
16//! fn main() {
17//! // Initialize PostHog client first.
18//! let _guard = better_posthog::init(better_posthog::ClientOptions {
19//! api_key: Some("phc_your_api_key".into()),
20//! ..Default::default()
21//! });
22//!
23//! tauri::Builder::default()
24//! .plugin(tauri_plugin_better_posthog::init())
25//! .run(tauri::generate_context!())
26//! .expect("error while running tauri application");
27//! }
28//! ```
29//!
30//! # Identity Strategies
31//!
32//! The plugin supports three identity strategies:
33//!
34//! - [`IdentityStrategy::Autogenerated`] (default): Persists a UUID v4 in the app data directory
35//! - [`IdentityStrategy::Custom`]: Use a developer-provided closure to resolve the user ID
36//! - [`IdentityStrategy::Anonymous`]: Each event gets a transient UUID v7
37//!
38//! # Example
39//!
40//! ```ignore
41//! use tauri_plugin_better_posthog::{Builder, IdentityStrategy, PostHogExt, PostHogEvent};
42//!
43//! // Custom identity from your user system.
44//! let plugin = Builder::new()
45//! .identity(IdentityStrategy::Custom(Box::new(|app_handle| {
46//! // Return user ID from your app's state.
47//! Some("user_123".to_string())
48//! })))
49//! .build();
50//!
51//! // Capture events from Rust
52//! app.capture_event(MyCustomEvent { ... });
53//! ```
54
55mod commands;
56mod identity;
57mod state;
58
59pub use identity::IdentityStrategy;
60use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
61use tauri::{Manager, Runtime};
62
63/// Initializes the plugin with default settings.
64#[must_use]
65pub fn init<R: Runtime>() -> TauriPlugin<R> {
66 Builder::default().build()
67}
68
69/// Builder for configuring the PostHog plugin.
70pub struct Builder<R: Runtime> {
71 identity_strategy: IdentityStrategy<R>,
72}
73
74impl<R: Runtime> Builder<R> {
75 /// Creates a new plugin builder with default settings.
76 #[must_use]
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 /// Sets the identity strategy for user identification.
82 ///
83 /// See [`IdentityStrategy`] for available options.
84 #[must_use]
85 pub fn identity(mut self, strategy: IdentityStrategy<R>) -> Self {
86 self.identity_strategy = strategy;
87 self
88 }
89
90 /// Builds the plugin with the configured settings.
91 #[must_use]
92 pub fn build(self) -> TauriPlugin<R> {
93 PluginBuilder::new("better-posthog")
94 .invoke_handler(tauri::generate_handler![commands::capture, commands::batch])
95 .setup(move |app, _api| {
96 let distinct_id = self.identity_strategy.resolve(app);
97
98 let state = state::PluginState::new(distinct_id);
99 app.manage(state);
100
101 Ok(())
102 })
103 .build()
104 }
105}
106
107impl<R: Runtime> Default for Builder<R> {
108 fn default() -> Self {
109 Self {
110 identity_strategy: IdentityStrategy::default(),
111 }
112 }
113}
114
115/// Extension trait for capturing PostHog events.
116///
117/// This trait is automatically implemented for any type that implements [`Manager`](tauri::Manager).
118pub trait PostHogExt<R: Runtime> {
119 /// Captures an event defined via the [`PostHogEvent`] trait.
120 fn capture_event(&self, event: impl PostHogEvent);
121
122 /// Captures a batch of events efficiently.
123 fn batch_events(&self, events: &[impl PostHogEvent]);
124}
125
126impl<R: Runtime, T: Manager<R>> PostHogExt<R> for T {
127 fn capture_event(&self, event: impl PostHogEvent) {
128 self.batch_events(&[event]);
129 }
130
131 fn batch_events(&self, events: &[impl PostHogEvent]) {
132 let state = self.state::<state::PluginState>();
133 let package_info = self.package_info();
134
135 let distinct_id = state.distinct_id();
136 let session_id = state.session_id();
137
138 better_posthog::events::batch(
139 events
140 .iter()
141 .map(|event| {
142 let event_name = event.name();
143 let properties = event.properties();
144
145 #[allow(clippy::option_if_let_else)]
146 let mut event = match distinct_id {
147 Some(id) => better_posthog::Event::new(event_name, id),
148 None => better_posthog::Event::new_anonymous(event_name),
149 };
150
151 for (key, value) in properties {
152 event.insert_property(key, value);
153 }
154
155 event.insert_property("$session_id".to_string(), session_id);
156
157 event.insert_property("$app".to_string(), package_info.name.clone());
158 event.insert_property("$app_version".to_string(), package_info.version.to_string());
159
160 #[cfg(target_os = "windows")]
161 event.insert_property("$browser".to_string(), "webview2");
162 #[cfg(not(target_os = "windows"))]
163 event.insert_property("$browser".to_string(), "webkit");
164 event.insert_property("$browser_version".to_string(), tauri::webview_version().ok());
165
166 event
167 })
168 .collect(),
169 );
170 }
171}
172
173/// Trait for defining custom reusable PostHog events.
174///
175/// # Example
176///
177/// ```ignore
178/// use std::collections::HashMap;
179/// use tauri_plugin_better_posthog::PostHogEvent;
180///
181/// struct ButtonClick {
182/// button_id: String,
183/// page: String,
184/// }
185///
186/// impl PostHogEvent for ButtonClick {
187/// fn name(&self) -> &str {
188/// "button_click"
189/// }
190///
191/// fn properties(&self) -> HashMap<String, serde_json::Value> {
192/// let mut properties = HashMap::new();
193/// properties.insert("button_id".to_string(), self.button_id.clone().into());
194/// properties.insert("page".to_string(), self.page.clone().into());
195/// properties
196/// }
197/// }
198/// ```
199pub trait PostHogEvent {
200 /// Returns the name of the event.
201 fn name(&self) -> &str;
202
203 /// Returns the custom properties associated with the event.
204 fn properties(&self) -> std::collections::HashMap<String, serde_json::Value>;
205}