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: Default::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: Default::default(),
66 }
67 }
68
69 #[cfg(mobile)]
70 fn new(handle: PluginHandle<R>) -> Self {
71 Self {
72 handle,
73 data: Default::default(),
74 }
75 }
76
77 pub fn id(mut self, id: i32) -> Self {
79 self.data.id = id;
80 self
81 }
82
83 pub fn channel_id(mut self, id: impl Into<String>) -> Self {
88 self.data.channel_id.replace(id.into());
89 self
90 }
91
92 pub fn title(mut self, title: impl Into<String>) -> Self {
94 self.data.title.replace(title.into());
95 self
96 }
97
98 pub fn body(mut self, body: impl Into<String>) -> Self {
100 self.data.body.replace(body.into());
101 self
102 }
103
104 pub fn schedule(mut self, schedule: Schedule) -> Self {
106 self.data.schedule.replace(schedule);
107 self
108 }
109
110 pub fn large_body(mut self, large_body: impl Into<String>) -> Self {
114 self.data.large_body.replace(large_body.into());
115 self
116 }
117
118 pub fn summary(mut self, summary: impl Into<String>) -> Self {
120 self.data.summary.replace(summary.into());
121 self
122 }
123
124 pub fn action_type_id(mut self, action_type_id: impl Into<String>) -> Self {
126 self.data.action_type_id.replace(action_type_id.into());
127 self
128 }
129
130 pub fn group(mut self, group: impl Into<String>) -> Self {
134 self.data.group.replace(group.into());
135 self
136 }
137
138 pub fn group_summary(mut self) -> Self {
140 self.data.group_summary = true;
141 self
142 }
143
144 pub fn sound(mut self, sound: impl Into<String>) -> Self {
146 self.data.sound.replace(sound.into());
147 self
148 }
149
150 pub fn inbox_line(mut self, line: impl Into<String>) -> Self {
156 self.data.inbox_lines.push(line.into());
157 self
158 }
159
160 pub fn icon(mut self, icon: impl Into<String>) -> Self {
164 self.data.icon.replace(icon.into());
165 self
166 }
167
168 pub fn large_icon(mut self, large_icon: impl Into<String>) -> Self {
172 self.data.large_icon.replace(large_icon.into());
173 self
174 }
175
176 pub fn icon_color(mut self, icon_color: impl Into<String>) -> Self {
178 self.data.icon_color.replace(icon_color.into());
179 self
180 }
181
182 pub fn attachment(mut self, attachment: Attachment) -> Self {
184 self.data.attachments.push(attachment);
185 self
186 }
187
188 pub fn extra(mut self, key: impl Into<String>, value: impl Serialize) -> Self {
190 if let Ok(value) = serde_json::to_value(value) {
191 self.data.extra.insert(key.into(), value);
192 }
193 self
194 }
195
196 pub fn ongoing(mut self) -> Self {
202 self.data.ongoing = true;
203 self
204 }
205
206 pub fn auto_cancel(mut self) -> Self {
208 self.data.auto_cancel = true;
209 self
210 }
211
212 pub fn silent(mut self) -> Self {
214 self.data.silent = true;
215 self
216 }
217}
218
219pub trait NotificationsExt<R: Runtime> {
221 fn notifications(&self) -> &Notifications<R>;
222}
223
224impl<R: Runtime, T: Manager<R>> crate::NotificationsExt<R> for T {
225 fn notifications(&self) -> &Notifications<R> {
226 self.state::<Notifications<R>>().inner()
227 }
228}
229
230pub fn init<R: Runtime>() -> TauriPlugin<R> {
232 Builder::new("notifications")
233 .invoke_handler(tauri::generate_handler![
234 commands::notify,
235 commands::request_permission,
236 commands::register_for_push_notifications,
237 commands::unregister_for_push_notifications,
238 commands::is_permission_granted,
239 commands::register_action_types,
240 commands::get_pending,
241 commands::get_active,
242 commands::set_click_listener_active,
243 commands::remove_active,
244 commands::cancel,
245 commands::cancel_all,
246 commands::create_channel,
247 commands::delete_channel,
248 commands::list_channels,
249 #[cfg(desktop)]
250 listeners::register_listener,
251 #[cfg(desktop)]
252 listeners::remove_listener,
253 ])
254 .setup(|app, api| {
255 #[cfg(desktop)]
256 listeners::init();
257 #[cfg(mobile)]
258 let notification = mobile::init(app, api)?;
259 #[cfg(all(desktop, feature = "notify-rust"))]
260 let notification = desktop::init(app, api)?;
261 #[cfg(all(target_os = "macos", not(feature = "notify-rust")))]
262 let notification = macos::init(app, api)?;
263 app.manage(notification);
264 Ok(())
265 })
266 .build()
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[cfg(desktop)]
275 fn create_test_data() -> NotificationData {
276 NotificationData::default()
277 }
278
279 #[cfg(mobile)]
280 fn create_test_data() -> NotificationData {
281 NotificationData::default()
282 }
283
284 #[test]
285 fn test_notification_data_id() {
286 let mut data = create_test_data();
287 data.id = 42;
288 assert_eq!(data.id, 42);
289 }
290
291 #[test]
292 fn test_notification_data_channel_id() {
293 let mut data = create_test_data();
294 data.channel_id = Some("test_channel".to_string());
295 assert_eq!(data.channel_id, Some("test_channel".to_string()));
296 }
297
298 #[test]
299 fn test_notification_data_title() {
300 let mut data = create_test_data();
301 data.title = Some("Test Title".to_string());
302 assert_eq!(data.title, Some("Test Title".to_string()));
303 }
304
305 #[test]
306 fn test_notification_data_body() {
307 let mut data = create_test_data();
308 data.body = Some("Test Body".to_string());
309 assert_eq!(data.body, Some("Test Body".to_string()));
310 }
311
312 #[test]
313 fn test_notification_data_large_body() {
314 let mut data = create_test_data();
315 data.large_body = Some("Large Body Text".to_string());
316 assert_eq!(data.large_body, Some("Large Body Text".to_string()));
317 }
318
319 #[test]
320 fn test_notification_data_summary() {
321 let mut data = create_test_data();
322 data.summary = Some("Summary Text".to_string());
323 assert_eq!(data.summary, Some("Summary Text".to_string()));
324 }
325
326 #[test]
327 fn test_notification_data_action_type_id() {
328 let mut data = create_test_data();
329 data.action_type_id = Some("action_type".to_string());
330 assert_eq!(data.action_type_id, Some("action_type".to_string()));
331 }
332
333 #[test]
334 fn test_notification_data_group() {
335 let mut data = create_test_data();
336 data.group = Some("test_group".to_string());
337 assert_eq!(data.group, Some("test_group".to_string()));
338 }
339
340 #[test]
341 fn test_notification_data_group_summary() {
342 let mut data = create_test_data();
343 data.group_summary = true;
344 assert!(data.group_summary);
345 }
346
347 #[test]
348 fn test_notification_data_sound() {
349 let mut data = create_test_data();
350 data.sound = Some("notification_sound".to_string());
351 assert_eq!(data.sound, Some("notification_sound".to_string()));
352 }
353
354 #[test]
355 fn test_notification_data_inbox_lines() {
356 let mut data = create_test_data();
357 data.inbox_lines.push("Line 1".to_string());
358 data.inbox_lines.push("Line 2".to_string());
359 assert_eq!(data.inbox_lines.len(), 2);
360 assert_eq!(data.inbox_lines[0], "Line 1");
361 assert_eq!(data.inbox_lines[1], "Line 2");
362 }
363
364 #[test]
365 fn test_notification_data_icon() {
366 let mut data = create_test_data();
367 data.icon = Some("icon_name".to_string());
368 assert_eq!(data.icon, Some("icon_name".to_string()));
369 }
370
371 #[test]
372 fn test_notification_data_large_icon() {
373 let mut data = create_test_data();
374 data.large_icon = Some("large_icon_name".to_string());
375 assert_eq!(data.large_icon, Some("large_icon_name".to_string()));
376 }
377
378 #[test]
379 fn test_notification_data_icon_color() {
380 let mut data = create_test_data();
381 data.icon_color = Some("#FF0000".to_string());
382 assert_eq!(data.icon_color, Some("#FF0000".to_string()));
383 }
384
385 #[test]
386 fn test_notification_data_attachments() {
387 let mut data = create_test_data();
388 let url = url::Url::parse("https://example.com/image.png").expect("Failed to parse URL");
389 let attachment = Attachment::new("attachment1", url);
390 data.attachments.push(attachment);
391 assert_eq!(data.attachments.len(), 1);
392 }
393
394 #[test]
395 fn test_notification_data_extra() {
396 let mut data = create_test_data();
397 data.extra
398 .insert("key1".to_string(), serde_json::json!("value1"));
399 data.extra.insert("key2".to_string(), serde_json::json!(42));
400 assert_eq!(data.extra.len(), 2);
401 assert_eq!(data.extra.get("key1"), Some(&serde_json::json!("value1")));
402 assert_eq!(data.extra.get("key2"), Some(&serde_json::json!(42)));
403 }
404
405 #[test]
406 fn test_notification_data_ongoing() {
407 let mut data = create_test_data();
408 data.ongoing = true;
409 assert!(data.ongoing);
410 }
411
412 #[test]
413 fn test_notification_data_auto_cancel() {
414 let mut data = create_test_data();
415 data.auto_cancel = true;
416 assert!(data.auto_cancel);
417 }
418
419 #[test]
420 fn test_notification_data_silent() {
421 let mut data = create_test_data();
422 data.silent = true;
423 assert!(data.silent);
424 }
425
426 #[test]
427 fn test_notification_data_schedule() {
428 let mut data = create_test_data();
429 let schedule = Schedule::Every {
430 interval: ScheduleEvery::Day,
431 count: 1,
432 allow_while_idle: false,
433 };
434 data.schedule = Some(schedule);
435 assert!(data.schedule.is_some());
436 assert!(matches!(data.schedule, Some(Schedule::Every { .. })));
437 }
438}