origin_notifications_tauri/
lib.rs1use async_trait::async_trait;
7use origin_domain::{AppError, Result};
8use origin_platform::{Notification, NotificationService, Urgency};
9use tauri::{AppHandle, Runtime};
10use tauri_plugin_notification::{NotificationExt, PermissionState};
11
12#[derive(Debug, Clone)]
13pub struct TauriNotificationService<R: Runtime> {
14 app: AppHandle<R>,
15}
16
17impl<R: Runtime> TauriNotificationService<R> {
18 pub fn new(app: AppHandle<R>) -> Self {
19 Self { app }
20 }
21
22 fn ensure_permission(&self) -> Result<bool> {
26 let state = self
27 .app
28 .notification()
29 .permission_state()
30 .map_err(|error| AppError::internal(format!("notification permission: {error}")))?;
31
32 let state = match state {
33 PermissionState::Prompt | PermissionState::PromptWithRationale => self
34 .app
35 .notification()
36 .request_permission()
37 .map_err(|error| {
38 AppError::internal(format!("notification permission request: {error}"))
39 })?,
40 decided => decided,
41 };
42
43 Ok(matches!(state, PermissionState::Granted))
44 }
45}
46
47#[async_trait]
48impl<R: Runtime> NotificationService for TauriNotificationService<R> {
49 async fn notify(&self, notification: Notification) -> Result<()> {
50 if !self.ensure_permission()? {
53 tracing::debug!(
54 title = %notification.title,
55 "notification suppressed: permission not granted"
56 );
57 return Ok(());
58 }
59
60 let mut builder = self.app.notification().builder().title(¬ification.title);
61
62 if let Some(body) = ¬ification.body {
63 builder = builder.body(body);
64 }
65 if let Some(tag) = ¬ification.tag {
66 builder = builder.group(tag);
68 }
69 if notification.urgency == Urgency::Critical {
70 builder = builder.sound("default");
71 }
72
73 builder
74 .show()
75 .map_err(|error| AppError::internal(format!("cannot show notification: {error}")))
76 }
77}