Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add implementation to support safari web push #57

Merged
merged 3 commits into from Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/lib.rs
Expand Up @@ -127,7 +127,7 @@ mod signer;

pub use crate::request::notification::{
CollapseId, LocalizedNotificationBuilder, NotificationBuilder, NotificationOptions, PlainNotificationBuilder,
Priority, SilentNotificationBuilder,
Priority, SilentNotificationBuilder, WebNotificationBuilder, WebPushAlert,
};

pub use crate::response::{ErrorBody, ErrorReason, Response};
Expand Down
2 changes: 2 additions & 0 deletions src/request/notification.rs
Expand Up @@ -4,11 +4,13 @@ mod localized;
mod options;
mod plain;
mod silent;
mod web;

pub use self::localized::{LocalizedAlert, LocalizedNotificationBuilder};
pub use self::options::{CollapseId, NotificationOptions, Priority};
pub use self::plain::PlainNotificationBuilder;
pub use self::silent::SilentNotificationBuilder;
pub use self::web::{WebNotificationBuilder, WebPushAlert};

use crate::request::payload::Payload;

Expand Down
1 change: 1 addition & 0 deletions src/request/notification/localized.rs
Expand Up @@ -315,6 +315,7 @@ impl<'a> NotificationBuilder<'a> for LocalizedNotificationBuilder<'a> {
content_available: None,
category: self.category,
mutable_content: Some(self.mutable_content),
url_args: None,
},
device_token,
options,
Expand Down
1 change: 1 addition & 0 deletions src/request/notification/plain.rs
Expand Up @@ -120,6 +120,7 @@ impl<'a> NotificationBuilder<'a> for PlainNotificationBuilder<'a> {
content_available: None,
category: self.category,
mutable_content: None,
url_args: None,
},
device_token,
options,
Expand Down
1 change: 1 addition & 0 deletions src/request/notification/silent.rs
Expand Up @@ -66,6 +66,7 @@ impl<'a> NotificationBuilder<'a> for SilentNotificationBuilder {
content_available: Some(self.content_available),
category: None,
mutable_content: None,
url_args: None,
},
device_token,
options,
Expand Down
127 changes: 127 additions & 0 deletions src/request/notification/web.rs
@@ -0,0 +1,127 @@
use crate::request::notification::{NotificationBuilder, NotificationOptions};
use crate::request::payload::{APSAlert, Payload, APS};
use std::collections::BTreeMap;

#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct WebPushAlert<'a> {
pub title: &'a str,
pub body: &'a str,
pub action: &'a str,
}

/// A builder to create a simple APNs notification payload.
///
/// # Example
///
/// ```rust
/// # use a2::request::notification::{NotificationBuilder, WebNotificationBuilder, WebPushAlert};
/// # fn main() {
/// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
/// builder.set_sound("prööt");
/// let payload = builder.build("device_id", Default::default())
/// .to_json_string().unwrap();
/// # }
/// ```
pub struct WebNotificationBuilder<'a> {
alert: WebPushAlert<'a>,
sound: Option<&'a str>,
url_args: &'a [&'a str],
}

impl<'a> WebNotificationBuilder<'a> {
/// Creates a new builder with the minimum amount of content.
///
/// ```rust
/// # use a2::request::notification::{WebNotificationBuilder, NotificationBuilder, WebPushAlert};
/// # fn main() {
/// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
/// let payload = builder.build("token", Default::default());
///
/// assert_eq!(
/// "{\"aps\":{\"alert\":{\"action\":\"View\",\"body\":\"World\",\"title\":\"Hello\"},\"url-args\":[\"arg1\"]}}",
/// &payload.to_json_string().unwrap()
/// );
/// # }
/// ```
pub fn new(alert: WebPushAlert<'a>, url_args: &'a [&'a str]) -> WebNotificationBuilder<'a> {
WebNotificationBuilder {
alert,
sound: None,
url_args: url_args,
}
}

/// File name of the custom sound to play when receiving the notification.
///
/// ```rust
/// # use a2::request::notification::{WebNotificationBuilder, NotificationBuilder, WebPushAlert};
/// # fn main() {
/// let mut builder = WebNotificationBuilder::new(WebPushAlert {title: "Hello", body: "World", action: "View"}, &["arg1"]);
/// builder.set_sound("meow");
/// let payload = builder.build("token", Default::default());
///
/// assert_eq!(
/// "{\"aps\":{\"alert\":{\"action\":\"View\",\"body\":\"World\",\"title\":\"Hello\"},\"sound\":\"meow\",\"url-args\":[\"arg1\"]}}",
/// &payload.to_json_string().unwrap()
/// );
/// # }
/// ```
pub fn set_sound(&mut self, sound: &'a str) -> &mut Self {
self.sound = Some(sound);
self
}
}

impl<'a> NotificationBuilder<'a> for WebNotificationBuilder<'a> {
fn build(self, device_token: &'a str, options: NotificationOptions<'a>) -> Payload<'a> {
Payload {
aps: APS {
alert: Some(APSAlert::WebPush(self.alert)),
badge: None,
sound: self.sound,
content_available: None,
category: None,
mutable_content: None,
url_args: Some(self.url_args),
},
device_token,
options,
data: BTreeMap::new(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_webpush_notification() {
let payload = WebNotificationBuilder::new(
WebPushAlert {
action: "View",
title: "Hello",
body: "world",
},
&vec!["arg1"],
)
.build("device-token", Default::default())
.to_json_string()
.unwrap();

let expected_payload = json!({
"aps": {
"alert": {
"body": "world",
"action": "View",
"title": "Hello"
},
"url-args": ["arg1"]
}
})
.to_string();

assert_eq!(expected_payload, payload);
}
}
7 changes: 6 additions & 1 deletion src/request/payload.rs
@@ -1,7 +1,7 @@
//! Payload with `aps` and custom data

use crate::error::Error;
use crate::request::notification::{LocalizedAlert, NotificationOptions};
use crate::request::notification::{LocalizedAlert, NotificationOptions, WebPushAlert};
use erased_serde::Serialize;
use serde_json::{self, Value};
use std::collections::BTreeMap;
Expand Down Expand Up @@ -117,6 +117,9 @@ pub struct APS<'a> {
/// displaying it to the user.
#[serde(skip_serializing_if = "Option::is_none")]
pub mutable_content: Option<u8>,

#[serde(skip_serializing_if = "Option::is_none")]
pub url_args: Option<&'a [&'a str]>,
}

/// Different notification content types.
Expand All @@ -127,4 +130,6 @@ pub enum APSAlert<'a> {
Plain(&'a str),
/// A rich localized notification.
Localized(LocalizedAlert<'a>),
/// Safari web push notification
WebPush(WebPushAlert<'a>),
}