use crate::client::{client::Client, error, message};
use reqwest::{Response, header::{CONTENT_TYPE, CONTENT_LENGTH, AUTHORIZATION, RETRY_AFTER}, StatusCode};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use tokio::time::sleep;
use super::{
error::{ErrorReason, FcmError, RetryAfter},
message::Message,
};
#[skip_serializing_none]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct FcmResponse {
pub message_id: Option<u64>,
pub error: Option<ErrorReason>,
pub multicast_id: Option<i64>,
pub success: Option<u64>,
pub failure: Option<u64>,
pub canonical_ids: Option<u64>,
pub results: Option<Vec<MessageResult>>,
}
#[skip_serializing_none]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Serialize)]
pub struct MessageResult {
pub message_id: Option<String>,
pub registration_id: Option<String>,
pub error: Option<ErrorReason>,
}
#[derive(Debug, Clone)]
pub struct FcmClient {
client: reqwest::Client,
api_key: String,
}
impl FcmClient {
pub fn new(api_key: String) -> Self {
Self {
client: reqwest::Client::new(),
api_key,
}
}
pub async fn send_message(&self, message: &Message) -> Result<FcmResponse, FcmError> {
let response = self.make_request(&message).await;
match response {
Err(FcmError::ServerError(Some(e))) => {
let delay = match e {
RetryAfter::Delay(delay) => delay,
RetryAfter::DateTime(date) => {
let now = chrono::Utc::now();
date.signed_duration_since(now)
}
};
sleep(delay.to_std().unwrap()).await;
return self.make_request(&message).await;
}
Err(e) => Err(e),
Ok(e) => Ok(e),
}
}
async fn decode_response(response: Response) -> Result<FcmResponse, FcmError> {
let response_status = response.status();
let retry_after = response
.headers()
.get(RETRY_AFTER)
.and_then(|ra| ra.to_str().ok())
.and_then(|ra| ra.parse::<RetryAfter>().ok());
match response_status {
StatusCode::OK => {
let fcm_response: FcmResponse = response.json().await.unwrap();
match fcm_response.error {
Some(ErrorReason::Unavailable) => Err(FcmError::ServerError(retry_after)),
Some(ErrorReason::InternalServerError) => Err(FcmError::ServerError(retry_after)),
_ => Ok(fcm_response),
}
}
StatusCode::UNAUTHORIZED => Err(FcmError::Unauthorized),
StatusCode::BAD_REQUEST => Err(FcmError::InvalidMessage("Bad Request".to_string())),
status if status.is_server_error() => Err(FcmError::ServerError(retry_after)),
_ => Err(FcmError::InvalidMessage("Unknown Error".to_string())),
}
}
pub async fn make_request(&self, message: &Message) -> Result<FcmResponse, FcmError> {
let url = format!("https://fcm.googleapis.com/fcm/send");
let response = self
.client
.post(&url)
.header(CONTENT_TYPE, "application/json")
.header(AUTHORIZATION, format!("key={}", self.api_key.clone()).as_bytes())
.bearer_auth(&self.api_key)
.json(&message)
.send()
.await;
Self::decode_response(response?).await
}
}
impl Client for FcmClient {
async fn send_by_token(&self, token: impl Into<String>, message: message::Message) -> Result<(), error::ClientError> {
let mut message: Message = message.into();
message.to = Some(token.into());
let result = self.send_message(&message).await;
if let Err(e) = result {
return Err(e.into());
} else {
let result = result.unwrap();
if let Some(error) = result.error {
return Err(error.into());
}
if result.success.unwrap_or_default() == 0{
return Err(error::ClientError::InvalidMessage("No message sent".to_string()));
}
}
Ok(())
}
async fn send_by_topic(&self, topic: impl Into<String>, message: message::Message) -> Result<(), error::ClientError>{
let mut message: Message = message.into();
message.to = Some(topic.into());
let result = self.send_message(&message).await;
if let Err(e) = result {
return Err(e.into());
} else {
let result = result.unwrap();
if let Some(error) = result.error {
return Err(error.into());
}
if result.success.unwrap_or_default() == 0{
return Err(error::ClientError::InvalidMessage("No message sent".to_string()));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::client::{client::Client, message::Message, notification::Notification};
use super::FcmClient;
#[tokio::test]
async fn test_send_message(){
let api_key = "AAAAeGlIzok:APA91bFYZmDhEWk06UABcQIjgV8Y1JQAZA2ZezBnrhE6HHw0rjmh8IB8fno9UTVyg8iBNnKWPSD9-E61NhYG7aS696x_zvkvv265-mlugoYMv9Jo0ofBdvVd4TJspkPJC3cztMRR6aCf";
let client = FcmClient::new(api_key.to_string());
let notification = Notification::new()
.title("test title")
.body(Some("test body".into()))
.build();
let message = Message::new()
.notification(Some(notification))
.build();
let fcm_message = message.into();
let response = client.send_message(&fcm_message).await;
dbg!(&response);
assert!(response.is_ok());
}
}