1mod types;
2
3use async_trait::async_trait;
4pub use self::types::*;
5
6use super::Error;
7
8use reqwest::header;
9
10pub struct Client {
11 cli: reqwest::Client,
12 production: bool,
13}
14
15impl Client {
16 pub fn new(certs: &[u8], password: &str) -> Result<Self, Error> {
17 let cert = reqwest::Identity::from_pkcs12_der(certs, password).unwrap();
18
19 let cli = reqwest::Client::builder().identity(cert).build().unwrap();
20
21 Ok(Self {
22 cli,
23 production: false,
24 })
25 }
26
27 pub async fn _push<'b>(&self, notification: &'b Notification<'_>) -> Result<Response, Error> {
28 let url = self.build_url(¬ification.device_token);
29
30 let mut headers = header::HeaderMap::new();
31
32 headers.append(
33 "apns-topic",
34 header::HeaderValue::from_str(notification.topic).unwrap(),
35 );
36
37 if let Some(id) = notification.id {
38 headers.append(
39 "apns-id",
40 header::HeaderValue::from_str(&id.to_string()).unwrap(),
41 );
42 }
43
44 if let Some(expire) = notification.expiration {
45 headers.append(
46 "apns-expiration",
47 header::HeaderValue::from_str(&expire.to_string()).unwrap(),
48 );
49 }
50
51 if let Some(collapse_id) = ¬ification.collapse_id {
52 headers.append(
53 "apns-collapse-id",
54 header::HeaderValue::from_str(collapse_id.as_str()).unwrap(),
55 );
56 }
57
58 if let Some(priority) = notification.priority {
59 headers.append(
60 "apns-priority",
61 header::HeaderValue::from_str(&priority.to_uint().to_string()).unwrap(),
62 );
63 }
64
65 if let Some(push_type) = ¬ification.apns_push_type {
66 headers.append(
67 "apns-push-type",
68 header::HeaderValue::from_str(push_type.as_str()).unwrap(),
69 );
70 }
71
72 let request = ApnsRequest {
73 aps: ¬ification.payload,
74 custom: notification.custom.as_ref(),
75 };
76 let resp = self
77 .cli
78 .post(&url)
79 .headers(headers)
80 .json(&request)
81 .send()
82 .await
83 .unwrap();
84
85 let status_code = resp.status();
86
87 let headers = resp.headers().clone();
88
89 let apns_id = headers.get("apns-id").unwrap().clone();
90
91 let apns_id = apns_id.to_str().unwrap();
92
93 let mut resp = resp.json::<Response>().await.unwrap();
94
95 resp.apns_id = apns_id.to_string();
96 resp.status_code = status_code;
97 resp.token = notification.device_token.to_string();
98
99 Ok(resp)
100 }
101
102 pub async fn push<'b>(
103 &self,
104 notification: &'b [Notification<'_>],
105 ) -> Result<BatchResponse, Error> {
106 let mut resps = BatchResponse::default();
107
108 for notify in notification {
109 let resp = self._push(notify).await;
110 match resp {
111 Ok(res) => {
112 if res.reason == ApiErrorReason::BadDeviceToken {
113 resps.responses.push(res);
114 }
115 }
116 Err(err) => {
117 resps.failure += 1
118 }
119 }
120 }
121 resps.failure = (notification.len() as i64 - resps.success) as i64;
122 Ok(resps)
123 }
124
125 fn build_url(&self, device_token: &str) -> String {
126 let root = if self.production {
127 APN_URL_PRODUCTION
128 } else {
129 APN_URL_DEV
130 };
131 format!("{}/3/device/{}", root, device_token)
132 }
133}
134
135#[async_trait]
136impl<'a> super::Pusher<'a, Notification<'a>, Response> for Client {
137 async fn push(&self, msg: &'a Notification) -> Result<Response, Error> {
138 self._push(msg).await
139 }
140}
141
142#[cfg(test)]
143mod test {
144 #[test]
145 fn test_cert() {
146
147 }
148}