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