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