Skip to main content

apns_h2/request/notification/
web.rs

1use crate::request::notification::{NotificationBuilder, NotificationOptions};
2use crate::request::payload::{APS, APSAlert, APSSound, Payload};
3use std::borrow::Cow;
4use std::collections::BTreeMap;
5
6#[derive(Serialize, Deserialize, Debug, Clone)]
7#[serde(rename_all = "kebab-case")]
8pub struct WebPushAlert<'a> {
9    pub title: &'a str,
10    pub body: &'a str,
11    pub action: &'a str,
12}
13
14/// A builder to create a simple APNs notification payload.
15///
16/// # Example
17///
18/// ```rust
19/// # use apns_h2::request::notification::{NotificationBuilder, WebNotificationBuilder, WebPushAlert};
20/// # use apns_h2::request::payload::PayloadLike;
21/// # fn main() {
22/// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
23/// builder.sound("prööt");
24/// let payload = builder.build("device_id", Default::default())
25///    .to_json_string().unwrap();
26/// # }
27/// ```
28pub struct WebNotificationBuilder<'a> {
29    alert: WebPushAlert<'a>,
30    sound: Option<Cow<'a, str>>,
31    url_args: Vec<Cow<'a, str>>,
32    interruption_level: Option<crate::request::payload::InterruptionLevel>,
33    dismissal_date: Option<u64>,
34}
35
36impl<'a> WebNotificationBuilder<'a> {
37    /// Creates a new builder with the minimum amount of content.
38    ///
39    /// ```rust
40    /// # use apns_h2::request::notification::{WebNotificationBuilder, NotificationBuilder, WebPushAlert};
41    /// # use apns_h2::request::payload::PayloadLike;
42    /// # fn main() {
43    /// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
44    /// let payload = builder.build("token", Default::default());
45    ///
46    /// assert_eq!(
47    ///     "{\"aps\":{\"alert\":{\"title\":\"Hello\",\"body\":\"World\",\"action\":\"View\"},\"url-args\":[\"arg1\"]}}",
48    ///     &payload.to_json_string().unwrap()
49    /// );
50    /// # }
51    /// ```
52    pub fn new<S>(alert: WebPushAlert<'a>, url_args: &'a [S]) -> WebNotificationBuilder<'a>
53    where
54        S: Into<Cow<'a, str>> + AsRef<str>,
55    {
56        WebNotificationBuilder {
57            alert,
58            sound: None,
59            url_args: url_args.iter().map(AsRef::as_ref).map(Into::into).collect(),
60            interruption_level: None,
61            dismissal_date: None,
62        }
63    }
64
65    /// File name of the custom sound to play when receiving the notification.
66    ///
67    /// ```rust
68    /// # use apns_h2::request::notification::{WebNotificationBuilder, NotificationBuilder, WebPushAlert};
69    /// # use apns_h2::request::payload::PayloadLike;
70    /// # fn main() {
71    /// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
72    /// builder.sound("meow");
73    /// let payload = builder.build("token", Default::default());
74    ///
75    /// assert_eq!(
76    ///     "{\"aps\":{\"alert\":{\"title\":\"Hello\",\"body\":\"World\",\"action\":\"View\"},\"sound\":\"meow\",\"url-args\":[\"arg1\"]}}",
77    ///     &payload.to_json_string().unwrap()
78    /// );
79    /// # }
80    /// ```
81    pub fn sound(&mut self, sound: impl Into<Cow<'a, str>>) -> &mut Self {
82        self.sound = Some(sound.into());
83        self
84    }
85
86    #[deprecated(
87        since = "0.11.0",
88        note = "Use the idiomatic `sound` instead of the legacy `set_*` fn"
89    )]
90    pub fn set_sound(&mut self, sound: impl Into<Cow<'a, str>>) -> &mut Self {
91        self.sound(sound)
92    }
93
94    /// Set the interruption level to active. The system presents the notification
95    /// immediately, lights up the screen, and can play a sound.
96    ///
97    /// ```rust
98    /// # use apns_h2::request::notification::{WebNotificationBuilder, NotificationBuilder, WebPushAlert};
99    /// # use apns_h2::request::payload::PayloadLike;
100    /// # fn main() {
101    /// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
102    /// builder.active_interruption_level();
103    /// let payload = builder.build("token", Default::default());
104    ///
105    /// assert_eq!(
106    ///     "{\"aps\":{\"alert\":{\"title\":\"Hello\",\"body\":\"World\",\"action\":\"View\"},\"interruption-level\":\"active\",\"url-args\":[\"arg1\"]}}",
107    ///     &payload.to_json_string().unwrap()
108    /// );
109    /// # }
110    /// ```
111    pub fn active_interruption_level(&mut self) -> &mut Self {
112        self.interruption_level = Some(crate::request::payload::InterruptionLevel::Active);
113        self
114    }
115
116    #[deprecated(
117        since = "0.11.0",
118        note = "Use the idiomatic `active_interruption_level` instead of the legacy `set_*` fn"
119    )]
120    pub fn set_active_interruption_level(&mut self) -> &mut Self {
121        self.active_interruption_level()
122    }
123
124    /// Set the interruption level to critical. The system presents the notification
125    /// immediately, lights up the screen, and bypasses the mute switch to play a sound.
126    ///
127    /// ```rust
128    /// # use apns_h2::request::notification::{WebNotificationBuilder, NotificationBuilder, WebPushAlert};
129    /// # use apns_h2::request::payload::PayloadLike;
130    /// # fn main() {
131    /// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
132    /// builder.critical_interruption_level();
133    /// let payload = builder.build("token", Default::default());
134    ///
135    /// assert_eq!(
136    ///     "{\"aps\":{\"alert\":{\"title\":\"Hello\",\"body\":\"World\",\"action\":\"View\"},\"interruption-level\":\"critical\",\"url-args\":[\"arg1\"]}}",
137    ///     &payload.to_json_string().unwrap()
138    /// );
139    /// # }
140    /// ```
141    pub fn critical_interruption_level(&mut self) -> &mut Self {
142        self.interruption_level = Some(crate::request::payload::InterruptionLevel::Critical);
143        self
144    }
145
146    #[deprecated(
147        since = "0.11.0",
148        note = "Use the idiomatic `critical_interruption_level` instead of the legacy `set_*` fn"
149    )]
150    pub fn set_critical_interruption_level(&mut self) -> &mut Self {
151        self.critical_interruption_level()
152    }
153
154    /// Set the interruption level to passive. The system adds the notification to
155    /// the notification list without lighting up the screen or playing a sound.
156    ///
157    /// ```rust
158    /// # use apns_h2::request::notification::{WebNotificationBuilder, NotificationBuilder, WebPushAlert};
159    /// # use apns_h2::request::payload::PayloadLike;
160    /// # fn main() {
161    /// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
162    /// builder.passive_interruption_level();
163    /// let payload = builder.build("token", Default::default());
164    ///
165    /// assert_eq!(
166    ///     "{\"aps\":{\"alert\":{\"title\":\"Hello\",\"body\":\"World\",\"action\":\"View\"},\"interruption-level\":\"passive\",\"url-args\":[\"arg1\"]}}",
167    ///     &payload.to_json_string().unwrap()
168    /// );
169    /// # }
170    /// ```
171    pub fn passive_interruption_level(&mut self) -> &mut Self {
172        self.interruption_level = Some(crate::request::payload::InterruptionLevel::Passive);
173        self
174    }
175
176    #[deprecated(
177        since = "0.11.0",
178        note = "Use the idiomatic `passive_interruption_level` instead of the legacy `set_*` fn"
179    )]
180    pub fn set_passive_interruption_level(&mut self) -> &mut Self {
181        self.passive_interruption_level()
182    }
183
184    /// Set the interruption level to time sensitive. The system presents the notification
185    /// immediately, lights up the screen, can play a sound, and breaks through system
186    /// notification controls.
187    ///
188    /// ```rust
189    /// # use apns_h2::request::notification::{WebNotificationBuilder, NotificationBuilder, WebPushAlert};
190    /// # use apns_h2::request::payload::PayloadLike;
191    /// # fn main() {
192    /// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
193    /// builder.time_sensitive_interruption_level();
194    /// let payload = builder.build("token", Default::default());
195    ///
196    /// assert_eq!(
197    ///     "{\"aps\":{\"alert\":{\"title\":\"Hello\",\"body\":\"World\",\"action\":\"View\"},\"interruption-level\":\"time-sensitive\",\"url-args\":[\"arg1\"]}}",
198    ///     &payload.to_json_string().unwrap()
199    /// );
200    /// # }
201    /// ```
202    pub fn time_sensitive_interruption_level(&mut self) -> &mut Self {
203        self.interruption_level = Some(crate::request::payload::InterruptionLevel::TimeSensitive);
204        self
205    }
206
207    #[deprecated(
208        since = "0.11.0",
209        note = "Use the idiomatic `time_sensitive_interruption_level` instead of the legacy `set_*` fn"
210    )]
211    pub fn set_time_sensitive_interruption_level(&mut self) -> &mut Self {
212        self.time_sensitive_interruption_level()
213    }
214
215    /// Set the interruption level directly. Controls how the notification is presented to the user.
216    ///
217    /// ```rust
218    /// # use apns_h2::request::notification::{WebNotificationBuilder, NotificationBuilder, WebPushAlert};
219    /// # use apns_h2::request::payload::{PayloadLike, InterruptionLevel};
220    /// # fn main() {
221    /// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
222    /// builder.interruption_level(InterruptionLevel::Active);
223    /// let payload = builder.build("token", Default::default());
224    ///
225    /// assert_eq!(
226    ///     "{\"aps\":{\"alert\":{\"title\":\"Hello\",\"body\":\"World\",\"action\":\"View\"},\"interruption-level\":\"active\",\"url-args\":[\"arg1\"]}}",
227    ///     &payload.to_json_string().unwrap()
228    /// );
229    /// # }
230    /// ```
231    pub fn interruption_level(&mut self, level: crate::request::payload::InterruptionLevel) -> &mut Self {
232        self.interruption_level = Some(level);
233        self
234    }
235
236    #[deprecated(
237        since = "0.11.0",
238        note = "Use the idiomatic `interruption_level` instead of the legacy `set_*` fn"
239    )]
240    pub fn set_interruption_level(&mut self, level: crate::request::payload::InterruptionLevel) -> &mut Self {
241        self.interruption_level(level)
242    }
243
244    /// Set the dismissal date for when the system should automatically remove the notification.
245    /// The timestamp should be in Unix epoch time (seconds since 1970-01-01 00:00:00 UTC).
246    ///
247    /// ```rust
248    /// # use apns_h2::request::notification::{WebNotificationBuilder, NotificationBuilder, WebPushAlert};
249    /// # use apns_h2::request::payload::PayloadLike;
250    /// # fn main() {
251    /// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
252    /// builder.dismissal_date(1672531200); // January 1, 2023 00:00:00 UTC
253    /// let payload = builder.build("token", Default::default());
254    ///
255    /// assert_eq!(
256    ///     "{\"aps\":{\"alert\":{\"title\":\"Hello\",\"body\":\"World\",\"action\":\"View\"},\"dismissal-date\":1672531200,\"url-args\":[\"arg1\"]}}",
257    ///     &payload.to_json_string().unwrap()
258    /// );
259    /// # }
260    /// ```
261    pub fn dismissal_date(&mut self, dismissal_date: u64) -> &mut Self {
262        self.dismissal_date = Some(dismissal_date);
263        self
264    }
265
266    #[deprecated(
267        since = "0.11.0",
268        note = "Use the idiomatic `dismissal_date` instead of the legacy `set_*` fn"
269    )]
270    pub fn set_dismissal_date(&mut self, dismissal_date: u64) -> &mut Self {
271        self.dismissal_date(dismissal_date)
272    }
273}
274
275impl<'a> NotificationBuilder<'a> for WebNotificationBuilder<'a> {
276    fn build(self, device_token: impl Into<Cow<'a, str>>, options: NotificationOptions<'a>) -> Payload<'a> {
277        Payload {
278            aps: APS {
279                alert: Some(APSAlert::WebPush(self.alert)),
280                badge: None,
281                sound: self.sound.map(APSSound::Sound),
282                thread_id: None,
283                content_available: None,
284                category: None,
285                mutable_content: None,
286                interruption_level: self.interruption_level,
287                dismissal_date: self.dismissal_date,
288                url_args: Some(self.url_args),
289                timestamp: None,
290                event: None,
291                content_state: None,
292                attributes_type: None,
293                attributes: None,
294                input_push_channel: None,
295                input_push_token: None,
296            },
297            device_token: device_token.into(),
298            options,
299            data: BTreeMap::new(),
300        }
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307    use crate::request::payload::PayloadLike;
308    use serde_json::Value;
309
310    #[test]
311    fn test_webpush_notification() {
312        let payload = WebNotificationBuilder::new(
313            WebPushAlert {
314                action: "View",
315                title: "Hello",
316                body: "world",
317            },
318            &["arg1"],
319        )
320        .build("device-token", Default::default())
321        .to_json_string()
322        .unwrap();
323
324        let expected_payload = json!({
325            "aps": {
326                "alert": {
327                    "title": "Hello",
328                    "body": "world",
329                    "action": "View",
330                },
331                "url-args": ["arg1"]
332            }
333        });
334
335        assert_eq!(expected_payload, serde_json::from_str::<Value>(&payload).unwrap());
336    }
337
338    #[test]
339    fn test_webpush_notification_with_dismissal_date() {
340        let mut builder = WebNotificationBuilder::new(
341            WebPushAlert {
342                action: "View",
343                title: "Hello",
344                body: "world",
345            },
346            &["arg1"],
347        );
348
349        builder.dismissal_date(1672531200); // January 1, 2023 00:00:00 UTC
350        let payload = builder
351            .build("device-token", Default::default())
352            .to_json_string()
353            .unwrap();
354
355        let expected_payload = json!({
356            "aps": {
357                "alert": {
358                    "title": "Hello",
359                    "body": "world",
360                    "action": "View",
361                },
362                "dismissal-date": 1672531200,
363                "url-args": ["arg1"]
364            }
365        });
366
367        assert_eq!(expected_payload, serde_json::from_str::<Value>(&payload).unwrap());
368    }
369}