push_messaging/fcm/
fcm_client.rs1use crate::client::{client::Client, error, message};
2use reqwest::{Response, header::{CONTENT_TYPE, CONTENT_LENGTH, AUTHORIZATION, RETRY_AFTER}, StatusCode};
3use serde::{Deserialize, Serialize};
4use serde_with::skip_serializing_none;
5use tokio::time::sleep;
6
7use super::{
8 error::{ErrorReason, FcmError, RetryAfter},
9 message::Message,
10};
11
12#[skip_serializing_none]
13#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
14pub struct FcmResponse {
15 pub message_id: Option<u64>,
16 pub error: Option<ErrorReason>,
17 pub multicast_id: Option<i64>,
18 pub success: Option<u64>,
19 pub failure: Option<u64>,
20 pub canonical_ids: Option<u64>,
21 pub results: Option<Vec<MessageResult>>,
22}
23#[skip_serializing_none]
24#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Serialize)]
25pub struct MessageResult {
26 pub message_id: Option<String>,
27 pub registration_id: Option<String>,
28 pub error: Option<ErrorReason>,
29}
30
31#[derive(Debug, Clone)]
32pub struct FcmClient {
33 client: reqwest::Client,
34 api_key: String,
35}
36
37impl FcmClient {
38 pub fn new(api_key: String) -> Self {
39 Self {
40 client: reqwest::Client::new(),
41 api_key,
42 }
43 }
44 pub async fn send_message(&self, message: &Message) -> Result<FcmResponse, FcmError> {
45 let response = self.make_request(&message).await;
46 match response {
47 Err(FcmError::ServerError(Some(e))) => {
48 let delay = match e {
50 RetryAfter::Delay(delay) => delay,
51 RetryAfter::DateTime(date) => {
52 let now = chrono::Utc::now();
53 date.signed_duration_since(now)
54 }
55 };
56 sleep(delay.to_std().unwrap()).await;
57 return self.make_request(&message).await;
58 }
59 Err(e) => Err(e),
60 Ok(e) => Ok(e),
61 }
62 }
63
64 async fn decode_response(response: Response) -> Result<FcmResponse, FcmError> {
65 let response_status = response.status();
66
67 let retry_after = response
68 .headers()
69 .get(RETRY_AFTER)
70 .and_then(|ra| ra.to_str().ok())
71 .and_then(|ra| ra.parse::<RetryAfter>().ok());
72
73 match response_status {
74 StatusCode::OK => {
75 let fcm_response: FcmResponse = response.json().await.unwrap();
76
77 match fcm_response.error {
78 Some(ErrorReason::Unavailable) => Err(FcmError::ServerError(retry_after)),
79 Some(ErrorReason::InternalServerError) => Err(FcmError::ServerError(retry_after)),
80 _ => Ok(fcm_response),
81 }
82 }
83 StatusCode::UNAUTHORIZED => Err(FcmError::Unauthorized),
84 StatusCode::BAD_REQUEST => Err(FcmError::InvalidMessage("Bad Request".to_string())),
85 status if status.is_server_error() => Err(FcmError::ServerError(retry_after)),
86 _ => Err(FcmError::InvalidMessage("Unknown Error".to_string())),
87 }
88 }
89 pub async fn make_request(&self, message: &Message) -> Result<FcmResponse, FcmError> {
90 let url = format!("https://fcm.googleapis.com/fcm/send");
91 let response = self
92 .client
93 .post(&url)
94 .header(CONTENT_TYPE, "application/json")
95 .header(AUTHORIZATION, format!("key={}", self.api_key.clone()).as_bytes())
96 .bearer_auth(&self.api_key)
97 .json(&message)
98 .send()
99 .await;
100 Self::decode_response(response?).await
101 }
102}
103
104impl Client for FcmClient {
105 async fn send_by_token(&self, token: impl Into<String>, message: message::Message) -> Result<(), error::ClientError> {
106 let mut message: Message = message.into();
107 message.to = Some(token.into());
108 let result = self.send_message(&message).await;
109 if let Err(e) = result {
110 return Err(e.into());
111 } else {
112 let result = result.unwrap();
113 if let Some(error) = result.error {
114 return Err(error.into());
115 }
116 if result.success.unwrap_or_default() == 0{
117 return Err(error::ClientError::InvalidMessage("No message sent".to_string()));
118 }
119 }
120
121 Ok(())
122 }
123 async fn send_by_topic(&self, topic: impl Into<String>, message: message::Message) -> Result<(), error::ClientError>{
124 let mut message: Message = message.into();
125 message.to = Some(topic.into());
126 let result = self.send_message(&message).await;
127 if let Err(e) = result {
128 return Err(e.into());
129 } else {
130 let result = result.unwrap();
131 if let Some(error) = result.error {
132 return Err(error.into());
133 }
134 if result.success.unwrap_or_default() == 0{
135 return Err(error::ClientError::InvalidMessage("No message sent".to_string()));
136 }
137 }
138
139 Ok(())
140 }
141
142}
143
144#[cfg(test)]
145mod tests {
146 use crate::client::{client::Client, message::Message, notification::Notification};
147
148 use super::FcmClient;
149 #[tokio::test]
150 async fn test_send_message(){
151 let api_key = "AAAAeGlIzok:APA91bFYZmDhEWk06UABcQIjgV8Y1JQAZA2ZezBnrhE6HHw0rjmh8IB8fno9UTVyg8iBNnKWPSD9-E61NhYG7aS696x_zvkvv265-mlugoYMv9Jo0ofBdvVd4TJspkPJC3cztMRR6aCf";
152 let client = FcmClient::new(api_key.to_string());
153 let notification = Notification::new()
154 .title("test title")
155 .body(Some("test body".into()))
156 .build();
157 let message = Message::new()
158 .notification(Some(notification))
159 .build();
160 let fcm_message = message.into();
161 let response = client.send_message(&fcm_message).await;
162 dbg!(&response);
163 assert!(response.is_ok());
164
165 }
166}