push_messaging/fcm/
fcm_client.rs

1use 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                //make same request again after delay in e
49                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}