Skip to content

Commit

Permalink
Add support for Topic header (#41) (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
depressed-pho authored Nov 14, 2023
1 parent cfd2c46 commit 836e018
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 0 deletions.
7 changes: 7 additions & 0 deletions src/clients/request_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ where
builder = builder.header("Urgency", urgency.to_string());
}

if let Some(topic) = message.topic {
builder = builder.header("Topic", topic);
}

if let Some(payload) = message.payload {
builder = builder
.header(CONTENT_ENCODING, payload.content_encoding.to_str())
Expand Down Expand Up @@ -116,14 +120,17 @@ mod tests {

builder.set_ttl(420);
builder.set_urgency(Urgency::VeryLow);
builder.set_topic("some-topic".into());

let request = build_request::<isahc::Body>(builder.build().unwrap());
let ttl = request.headers().get("TTL").unwrap().to_str().unwrap();
let urgency = request.headers().get("Urgency").unwrap().to_str().unwrap();
let topic = request.headers().get("Topic").unwrap().to_str().unwrap();
let expected_uri: Uri = "fcm.googleapis.com".parse().unwrap();

assert_eq!("420", ttl);
assert_eq!("very-low", urgency);
assert_eq!("some-topic", topic);
assert_eq!(expected_uri.host(), request.uri().host());
}

Expand Down
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub enum WebPushError {
InvalidPackageName,
/// The TTL value provided was not valid or was not provided
InvalidTtl,
/// The Topic value provided was invalid
InvalidTopic,
/// The request was missing required crypto keys
MissingCryptoKeys,
/// One or more of the crypto key elements are invalid.
Expand Down Expand Up @@ -109,6 +111,7 @@ impl WebPushError {
WebPushError::TlsError => "tls_error",
WebPushError::InvalidPackageName => "invalid_package_name",
WebPushError::InvalidTtl => "invalid_ttl",
WebPushError::InvalidTopic => "invalid_topic",
WebPushError::InvalidResponse => "invalid_response",
WebPushError::MissingCryptoKeys => "missing_crypto_keys",
WebPushError::InvalidCryptoKeys => "invalid_crypto_keys",
Expand Down Expand Up @@ -150,6 +153,7 @@ impl fmt::Display for WebPushError {
WebPushError::InvalidPackageName =>
write!(f, "Make sure the message was addressed to a registration token whose package name matches the value passed in the request."),
WebPushError::InvalidTtl => write!(f, "The TTL value provided was not valid or was not provided"),
WebPushError::InvalidTopic => write!(f, "The Topic value provided was invalid"),
WebPushError::InvalidResponse => write!(f, "The response data couldn't be parses"),
WebPushError::MissingCryptoKeys => write!(f, "The request is missing cryptographic keys"),
WebPushError::InvalidCryptoKeys => write!(f, "The request is having invalid cryptographic keys"),
Expand Down
34 changes: 34 additions & 0 deletions src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ pub struct WebPushMessage {
pub ttl: u32,
/// The urgency of the message (very-low | low | normal | high)
pub urgency: Option<Urgency>,
/// The topic of the mssage
pub topic: Option<String>,
/// The encrypted request payload, if sending any data.
pub payload: Option<WebPushPayload>,
}
Expand All @@ -102,6 +104,7 @@ pub struct WebPushMessageBuilder<'a> {
payload: Option<WebPushPayloadBuilder<'a>>,
ttl: u32,
urgency: Option<Urgency>,
topic: Option<String>,
vapid_signature: Option<VapidSignature>,
}

Expand All @@ -115,6 +118,7 @@ impl<'a> WebPushMessageBuilder<'a> {
subscription_info,
ttl: 2_419_200,
urgency: None,
topic: None,
payload: None,
vapid_signature: None,
}
Expand All @@ -136,6 +140,18 @@ impl<'a> WebPushMessageBuilder<'a> {
self.urgency = Some(urgency);
}

/// Assign a topic to the push message. A message that has been stored
/// by the push service can be replaced with new content if the message
/// has been assigned a topic. If the user agent is offline during the
/// time that the push messages are sent, updating a push message avoid
/// the situation where outdated or redundant messages are sent to the
/// user agent. A message with a topic replaces any outstanding push
/// messages with an identical topic. It is an arbitrary string
/// consisting of at most 32 base64url characters.
pub fn set_topic(&mut self, topic: String) {
self.topic = Some(topic);
}

/// Add a VAPID signature to the request. To be generated with the
/// [VapidSignatureBuilder](struct.VapidSignatureBuilder.html).
pub fn set_vapid_signature(&mut self, vapid_signature: VapidSignature) {
Expand All @@ -153,6 +169,18 @@ impl<'a> WebPushMessageBuilder<'a> {
/// Builds and if set, encrypts the payload.
pub fn build(self) -> Result<WebPushMessage, WebPushError> {
let endpoint: Uri = self.subscription_info.endpoint.parse()?;
let topic: Option<String> = self
.topic
.map(|topic| {
if topic.len() > 32 {
Err(WebPushError::InvalidTopic)
} else if topic.chars().all(is_base64url_char) {
Ok(topic)
} else {
Err(WebPushError::InvalidTopic)
}
})
.transpose()?;

if let Some(payload) = self.payload {
let p256dh = base64::decode_config(&self.subscription_info.keys.p256dh, base64::URL_SAFE)?;
Expand All @@ -164,15 +192,21 @@ impl<'a> WebPushMessageBuilder<'a> {
endpoint,
ttl: self.ttl,
urgency: self.urgency,
topic,
payload: Some(http_ece.encrypt(payload.content)?),
})
} else {
Ok(WebPushMessage {
endpoint,
ttl: self.ttl,
urgency: self.urgency,
topic,
payload: None,
})
}
}
}

fn is_base64url_char(c: char) -> bool {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-' || c == '_');

Check warning on line 211 in src/message.rs

View workflow job for this annotation

GitHub Actions / clippy

manual `RangeInclusive::contains` implementation

warning: manual `RangeInclusive::contains` implementation --> src/message.rs:211:64 | 211 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-' || c == '_'); | ^^^^^^^^^^^^^^^^^^^^^^ help: use: `('0'..='9').contains(&c)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains

Check warning on line 211 in src/message.rs

View workflow job for this annotation

GitHub Actions / clippy

manual `RangeInclusive::contains` implementation

warning: manual `RangeInclusive::contains` implementation --> src/message.rs:211:38 | 211 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-' || c == '_'); | ^^^^^^^^^^^^^^^^^^^^^^ help: use: `('a'..='z').contains(&c)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains

Check warning on line 211 in src/message.rs

View workflow job for this annotation

GitHub Actions / clippy

manual `RangeInclusive::contains` implementation

warning: manual `RangeInclusive::contains` implementation --> src/message.rs:211:12 | 211 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-' || c == '_'); | ^^^^^^^^^^^^^^^^^^^^^^ help: use: `('A'..='Z').contains(&c)` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains = note: `#[warn(clippy::manual_range_contains)]` on by default

Check warning on line 211 in src/message.rs

View workflow job for this annotation

GitHub Actions / clippy

unneeded `return` statement

warning: unneeded `return` statement --> src/message.rs:211:5 | 211 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-' || c == '_'); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return = note: `#[warn(clippy::needless_return)]` on by default help: remove `return` | 211 - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-' || c == '_'); 211 + (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '-' || c == '_') |
}

0 comments on commit 836e018

Please sign in to comment.