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}