tauri_plugin_notifications/
lib.rs1use serde::Serialize;
4#[cfg(mobile)]
5use tauri::plugin::PluginHandle;
6#[cfg(desktop)]
7use tauri::AppHandle;
8use tauri::{
9 plugin::{Builder, TauriPlugin},
10 Manager, Runtime,
11};
12
13pub use models::*;
14pub use tauri::plugin::PermissionState;
15
16#[cfg(all(desktop, any(feature = "notify-rust", target_os = "linux")))]
17mod desktop;
18#[cfg(all(target_os = "macos", not(feature = "notify-rust")))]
19mod macos;
20#[cfg(mobile)]
21mod mobile;
22#[cfg(all(desktop, target_os = "linux", feature = "push-notifications"))]
23mod unifiedpush;
24#[cfg(all(target_os = "windows", not(feature = "notify-rust")))]
25mod windows;
26
27mod commands;
28mod error;
29#[cfg(desktop)]
30mod listeners;
31mod models;
32
33pub use error::{Error, Result};
34
35#[cfg(all(desktop, any(feature = "notify-rust", target_os = "linux")))]
36pub use desktop::Notifications;
37#[cfg(all(target_os = "macos", not(feature = "notify-rust")))]
38pub use macos::Notifications;
39#[cfg(mobile)]
40pub use mobile::Notifications;
41#[cfg(all(target_os = "windows", not(feature = "notify-rust")))]
42pub use windows::Notifications;
43
44#[derive(Debug)]
46pub struct NotificationsBuilder<R: Runtime> {
47 #[cfg(desktop)]
48 #[allow(dead_code)]
49 app: AppHandle<R>,
50 #[cfg(all(target_os = "macos", not(feature = "notify-rust")))]
51 plugin: std::sync::Arc<macos::NotificationPlugin>,
52 #[cfg(all(target_os = "windows", not(feature = "notify-rust")))]
53 plugin: std::sync::Arc<windows::WindowsPlugin>,
54 #[cfg(mobile)]
55 handle: PluginHandle<R>,
56 pub(crate) data: NotificationData,
57}
58
59impl<R: Runtime> NotificationsBuilder<R> {
60 #[cfg(all(desktop, any(feature = "notify-rust", target_os = "linux")))]
61 fn new(app: AppHandle<R>) -> Self {
62 Self {
63 app,
64 data: NotificationData::default(),
65 }
66 }
67
68 #[cfg(all(target_os = "macos", not(feature = "notify-rust")))]
69 fn new(app: AppHandle<R>, plugin: std::sync::Arc<macos::NotificationPlugin>) -> Self {
70 Self {
71 app,
72 plugin,
73 data: NotificationData::default(),
74 }
75 }
76
77 #[cfg(all(target_os = "windows", not(feature = "notify-rust")))]
78 fn new(app: AppHandle<R>, plugin: std::sync::Arc<windows::WindowsPlugin>) -> Self {
79 Self {
80 app,
81 plugin,
82 data: Default::default(),
83 }
84 }
85
86 #[cfg(mobile)]
87 fn new(handle: PluginHandle<R>) -> Self {
88 Self {
89 handle,
90 data: NotificationData::default(),
91 }
92 }
93
94 #[must_use]
96 pub const fn id(mut self, id: i32) -> Self {
97 self.data.id = id;
98 self
99 }
100
101 #[must_use]
106 pub fn channel_id(mut self, id: impl Into<String>) -> Self {
107 self.data.channel_id.replace(id.into());
108 self
109 }
110
111 #[must_use]
113 pub fn title(mut self, title: impl Into<String>) -> Self {
114 self.data.title.replace(title.into());
115 self
116 }
117
118 #[must_use]
120 pub fn body(mut self, body: impl Into<String>) -> Self {
121 self.data.body.replace(body.into());
122 self
123 }
124
125 #[must_use]
127 pub fn schedule(mut self, schedule: Schedule) -> Self {
128 self.data.schedule.replace(schedule);
129 self
130 }
131
132 #[must_use]
136 pub fn large_body(mut self, large_body: impl Into<String>) -> Self {
137 self.data.large_body.replace(large_body.into());
138 self
139 }
140
141 #[must_use]
143 pub fn summary(mut self, summary: impl Into<String>) -> Self {
144 self.data.summary.replace(summary.into());
145 self
146 }
147
148 #[must_use]
150 pub fn action_type_id(mut self, action_type_id: impl Into<String>) -> Self {
151 self.data.action_type_id.replace(action_type_id.into());
152 self
153 }
154
155 #[must_use]
159 pub fn group(mut self, group: impl Into<String>) -> Self {
160 self.data.group.replace(group.into());
161 self
162 }
163
164 #[must_use]
166 pub const fn group_summary(mut self) -> Self {
167 self.data.group_summary = true;
168 self
169 }
170
171 #[must_use]
173 pub fn sound(mut self, sound: impl Into<String>) -> Self {
174 self.data.sound.replace(sound.into());
175 self
176 }
177
178 #[must_use]
184 pub fn inbox_line(mut self, line: impl Into<String>) -> Self {
185 self.data.inbox_lines.push(line.into());
186 self
187 }
188
189 #[must_use]
193 pub fn icon(mut self, icon: impl Into<String>) -> Self {
194 self.data.icon.replace(icon.into());
195 self
196 }
197
198 #[must_use]
202 pub fn large_icon(mut self, large_icon: impl Into<String>) -> Self {
203 self.data.large_icon.replace(large_icon.into());
204 self
205 }
206
207 #[must_use]
209 pub fn icon_color(mut self, icon_color: impl Into<String>) -> Self {
210 self.data.icon_color.replace(icon_color.into());
211 self
212 }
213
214 #[must_use]
216 pub fn attachment(mut self, attachment: Attachment) -> Self {
217 self.data.attachments.push(attachment);
218 self
219 }
220
221 #[must_use]
223 pub fn extra(mut self, key: impl Into<String>, value: impl Serialize) -> Self {
224 if let Ok(value) = serde_json::to_value(value) {
225 self.data.extra.insert(key.into(), value);
226 }
227 self
228 }
229
230 #[must_use]
236 pub const fn ongoing(mut self) -> Self {
237 self.data.ongoing = true;
238 self
239 }
240
241 #[must_use]
243 pub const fn auto_cancel(mut self) -> Self {
244 self.data.auto_cancel = true;
245 self
246 }
247
248 #[must_use]
250 pub const fn silent(mut self) -> Self {
251 self.data.silent = true;
252 self
253 }
254}
255
256pub trait NotificationsExt<R: Runtime> {
258 fn notifications(&self) -> &Notifications<R>;
259}
260
261impl<R: Runtime, T: Manager<R>> crate::NotificationsExt<R> for T {
262 fn notifications(&self) -> &Notifications<R> {
263 self.state::<Notifications<R>>().inner()
264 }
265}
266
267#[must_use]
269pub fn init<R: Runtime>() -> TauriPlugin<R> {
270 Builder::new("notifications")
271 .invoke_handler(tauri::generate_handler![
272 commands::notify,
273 commands::request_permission,
274 commands::register_for_push_notifications,
275 commands::unregister_for_push_notifications,
276 commands::is_permission_granted,
277 commands::register_action_types,
278 commands::get_pending,
279 commands::get_active,
280 commands::set_click_listener_active,
281 commands::remove_active,
282 commands::remove_all,
283 commands::cancel,
284 commands::cancel_all,
285 commands::create_channel,
286 commands::delete_channel,
287 commands::list_channels,
288 #[cfg(desktop)]
289 listeners::register_listener,
290 #[cfg(desktop)]
291 listeners::remove_listener,
292 #[cfg(all(desktop, target_os = "linux", feature = "push-notifications"))]
293 commands::list_distributors,
294 #[cfg(all(desktop, target_os = "linux", feature = "push-notifications"))]
295 commands::set_distributor,
296 #[cfg(all(desktop, target_os = "linux", feature = "push-notifications"))]
297 commands::set_token,
298 ])
299 .setup(|app, api| {
300 #[cfg(desktop)]
301 listeners::init();
302 #[cfg(mobile)]
303 let notification = mobile::init(app, api)?;
304 #[cfg(all(desktop, any(feature = "notify-rust", target_os = "linux")))]
305 let notification = desktop::init(app, api)?;
306 #[cfg(all(target_os = "macos", not(feature = "notify-rust")))]
307 let notification = macos::init(app, api)?;
308 #[cfg(all(target_os = "windows", not(feature = "notify-rust")))]
309 let notification = windows::init(app, api)?;
310 app.manage(notification);
311 Ok(())
312 })
313 .build()
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[cfg(desktop)]
322 fn create_test_data() -> NotificationData {
323 NotificationData::default()
324 }
325
326 #[cfg(mobile)]
327 fn create_test_data() -> NotificationData {
328 NotificationData::default()
329 }
330
331 #[test]
332 fn test_notification_data_id() {
333 let mut data = create_test_data();
334 data.id = 42;
335 assert_eq!(data.id, 42);
336 }
337
338 #[test]
339 fn test_notification_data_channel_id() {
340 let mut data = create_test_data();
341 data.channel_id = Some("test_channel".to_string());
342 assert_eq!(data.channel_id, Some("test_channel".to_string()));
343 }
344
345 #[test]
346 fn test_notification_data_title() {
347 let mut data = create_test_data();
348 data.title = Some("Test Title".to_string());
349 assert_eq!(data.title, Some("Test Title".to_string()));
350 }
351
352 #[test]
353 fn test_notification_data_body() {
354 let mut data = create_test_data();
355 data.body = Some("Test Body".to_string());
356 assert_eq!(data.body, Some("Test Body".to_string()));
357 }
358
359 #[test]
360 fn test_notification_data_large_body() {
361 let mut data = create_test_data();
362 data.large_body = Some("Large Body Text".to_string());
363 assert_eq!(data.large_body, Some("Large Body Text".to_string()));
364 }
365
366 #[test]
367 fn test_notification_data_summary() {
368 let mut data = create_test_data();
369 data.summary = Some("Summary Text".to_string());
370 assert_eq!(data.summary, Some("Summary Text".to_string()));
371 }
372
373 #[test]
374 fn test_notification_data_action_type_id() {
375 let mut data = create_test_data();
376 data.action_type_id = Some("action_type".to_string());
377 assert_eq!(data.action_type_id, Some("action_type".to_string()));
378 }
379
380 #[test]
381 fn test_notification_data_group() {
382 let mut data = create_test_data();
383 data.group = Some("test_group".to_string());
384 assert_eq!(data.group, Some("test_group".to_string()));
385 }
386
387 #[test]
388 fn test_notification_data_group_summary() {
389 let mut data = create_test_data();
390 data.group_summary = true;
391 assert!(data.group_summary);
392 }
393
394 #[test]
395 fn test_notification_data_sound() {
396 let mut data = create_test_data();
397 data.sound = Some("notification_sound".to_string());
398 assert_eq!(data.sound, Some("notification_sound".to_string()));
399 }
400
401 #[test]
402 fn test_notification_data_inbox_lines() {
403 let mut data = create_test_data();
404 data.inbox_lines.push("Line 1".to_string());
405 data.inbox_lines.push("Line 2".to_string());
406 assert_eq!(data.inbox_lines.len(), 2);
407 assert_eq!(data.inbox_lines[0], "Line 1");
408 assert_eq!(data.inbox_lines[1], "Line 2");
409 }
410
411 #[test]
412 fn test_notification_data_icon() {
413 let mut data = create_test_data();
414 data.icon = Some("icon_name".to_string());
415 assert_eq!(data.icon, Some("icon_name".to_string()));
416 }
417
418 #[test]
419 fn test_notification_data_large_icon() {
420 let mut data = create_test_data();
421 data.large_icon = Some("large_icon_name".to_string());
422 assert_eq!(data.large_icon, Some("large_icon_name".to_string()));
423 }
424
425 #[test]
426 fn test_notification_data_icon_color() {
427 let mut data = create_test_data();
428 data.icon_color = Some("#FF0000".to_string());
429 assert_eq!(data.icon_color, Some("#FF0000".to_string()));
430 }
431
432 #[test]
433 fn test_notification_data_attachments() {
434 let mut data = create_test_data();
435 let url = url::Url::parse("https://example.com/image.png").expect("Failed to parse URL");
436 let attachment = Attachment::new("attachment1", url);
437 data.attachments.push(attachment);
438 assert_eq!(data.attachments.len(), 1);
439 }
440
441 #[test]
442 fn test_notification_data_extra() {
443 let mut data = create_test_data();
444 data.extra
445 .insert("key1".to_string(), serde_json::json!("value1"));
446 data.extra.insert("key2".to_string(), serde_json::json!(42));
447 assert_eq!(data.extra.len(), 2);
448 assert_eq!(data.extra.get("key1"), Some(&serde_json::json!("value1")));
449 assert_eq!(data.extra.get("key2"), Some(&serde_json::json!(42)));
450 }
451
452 #[test]
453 fn test_notification_data_ongoing() {
454 let mut data = create_test_data();
455 data.ongoing = true;
456 assert!(data.ongoing);
457 }
458
459 #[test]
460 fn test_notification_data_auto_cancel() {
461 let mut data = create_test_data();
462 data.auto_cancel = true;
463 assert!(data.auto_cancel);
464 }
465
466 #[test]
467 fn test_notification_data_silent() {
468 let mut data = create_test_data();
469 data.silent = true;
470 assert!(data.silent);
471 }
472
473 #[test]
474 fn test_notification_data_schedule() {
475 let mut data = create_test_data();
476 let schedule = Schedule::Every {
477 interval: ScheduleEvery::Day,
478 count: 1,
479 allow_while_idle: false,
480 };
481 data.schedule = Some(schedule);
482 assert!(data.schedule.is_some());
483 assert!(matches!(data.schedule, Some(Schedule::Every { .. })));
484 }
485}