diff --git a/services/tunnelbroker/src/notifs/apns/error.rs b/services/tunnelbroker/src/notifs/apns/error.rs --- a/services/tunnelbroker/src/notifs/apns/error.rs +++ b/services/tunnelbroker/src/notifs/apns/error.rs @@ -4,6 +4,7 @@ pub enum Error { JWTError, ReqwestError(reqwest::Error), + InvalidHeaderValue(reqwest::header::InvalidHeaderValue), } impl From for Error { diff --git a/services/tunnelbroker/src/notifs/apns/headers.rs b/services/tunnelbroker/src/notifs/apns/headers.rs new file mode 100644 --- /dev/null +++ b/services/tunnelbroker/src/notifs/apns/headers.rs @@ -0,0 +1,67 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub enum PushType { + /// The push type for notifications that trigger a user interaction, + /// for example, an alert, badge, or sound. + #[default] + Alert, + /// The push type for notifications that deliver content in the background, + /// and don’t trigger any user interactions. + Background, + /// The push type for notifications that request a user’s location. + Location, + /// The push type for notifications that provide information about + /// an incoming Voice-over-IP (VoIP) call. + Voip, + /// The push type for notifications that contain update information for a + /// watchOS app’s complications. + Complication, + /// The push type to signal changes to a File Provider extension. + FileProvider, + /// The push type for notifications that tell managed devices to contact the + /// MDM server. + Mdm, + /// The push type to signal changes to a live activity session. + LiveActivity, + /// The push type for notifications that provide information about updates to + /// your application’s push to talk services. + PushToTalk, +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct NotificationHeaders { + /// The value of this header must accurately reflect the contents of your + /// notification’s payload. If there’s a mismatch, or if the header is + /// missing on required systems, APNs may return an error, delay the + /// delivery of the notification, or drop it altogether. + pub apns_push_type: Option, + + /// A canonical UUID that’s the unique ID for the notification. + pub apns_id: Option, + + /// The date at which the notification is no longer valid. This value + /// is a UNIX epoch expressed in seconds (UTC). If the value is nonzero, + /// APNs stores the notification and tries to deliver it at least once, + /// repeating the attempt as needed until the specified date. If the value + /// is 0, APNs attempts to deliver the notification only once and doesn’t + /// store it. + pub apns_expiration: Option, + + /// The priority of the notification. If you omit this header, APNs sets the + /// notification priority to 10. + /// 10 - send the notification immediately + /// 5 - send the notification based on power considerations on the user’s + /// device + /// 1 - prioritize the device’s power considerations over all other + /// factors for delivery, and prevent awakening the device. + pub apns_priority: Option, + + /// The topic for the notification. + pub apns_topic: Option, + + /// An identifier you use to merge multiple notifications into a single + /// notification for the user. + pub apns_collapse_id: Option, +} diff --git a/services/tunnelbroker/src/notifs/apns/mod.rs b/services/tunnelbroker/src/notifs/apns/mod.rs --- a/services/tunnelbroker/src/notifs/apns/mod.rs +++ b/services/tunnelbroker/src/notifs/apns/mod.rs @@ -1,8 +1,11 @@ use crate::notifs::apns::config::APNsConfig; +use crate::notifs::apns::headers::{NotificationHeaders, PushType}; use crate::notifs::apns::token::APNsToken; +use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; use std::time::Duration; pub mod config; pub mod error; +mod headers; pub mod token; #[derive(Clone)] @@ -29,4 +32,57 @@ is_prod: config.production, }) } + + async fn build_headers( + &self, + notif_headers: NotificationHeaders, + ) -> Result { + let mut headers = HeaderMap::new(); + + headers.insert( + reqwest::header::CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ); + + let bearer = self.token.get_bearer().await?; + let token = format!("bearer {bearer}"); + headers.insert(AUTHORIZATION, HeaderValue::from_str(&token)?); + + if let Some(apns_topic) = ¬if_headers.apns_topic { + headers.insert("apns-topic", HeaderValue::from_str(apns_topic)?); + } + + if let Some(apns_id) = ¬if_headers.apns_id { + headers.insert("apns-id", HeaderValue::from_str(apns_id)?); + } + + if let Some(push_type) = ¬if_headers.apns_push_type { + let push_type_str = match push_type { + PushType::Alert => "alert", + PushType::Background => "background", + PushType::Location => "location", + PushType::Voip => "voip", + PushType::Complication => "complication", + PushType::FileProvider => "fileprovider", + PushType::Mdm => "mdm", + PushType::LiveActivity => "live", + PushType::PushToTalk => "pushtotalk", + }; + headers.insert("apns-push-type", HeaderValue::from_static(push_type_str)); + } + + if let Some(expiration) = notif_headers.apns_expiration { + headers.insert("apns-expiration", HeaderValue::from(expiration)); + } + + if let Some(priority) = notif_headers.apns_priority { + headers.insert("apns-priority", HeaderValue::from(priority)); + } + + if let Some(collapse_id) = ¬if_headers.apns_collapse_id { + headers.insert("apns-collapse-id", HeaderValue::from_str(collapse_id)?); + } + + Ok(headers) + } }