use crate::error::FcmError;
use crate::models::{FcmSendRequest, FcmSendResult, FcmSuccessResponse, Message};
use reqwest::Client;
use yup_oauth2::authenticator::Authenticator;
use yup_oauth2::hyper::client::HttpConnector;
use yup_oauth2::hyper_rustls::HttpsConnector;
use yup_oauth2::{read_service_account_key, ServiceAccountAuthenticator};
pub struct FcmClient {
auth: Authenticator<HttpsConnector<HttpConnector>>,
http_client: Client,
project_id: String,
}
impl FcmClient {
pub async fn new(service_account_key_path: &str) -> Result<Self, FcmError> {
let secret = read_service_account_key(service_account_key_path).await?;
let project_id = match secret.project_id {
Some(ref id) => id.clone(),
None => {
return Err(FcmError::AuthError(
"Service account key JSON file missing project ID".to_string(),
));
}
};
let auth = ServiceAccountAuthenticator::builder(secret).build().await?;
Ok(Self {
auth,
http_client: Client::new(),
project_id,
})
}
pub async fn send(&self, message: Message) -> Result<FcmSuccessResponse, FcmError> {
let url = self.build_url();
let token_str = self.get_token().await?;
let request = FcmSendRequest { message };
let response = self
.http_client
.post(url)
.header("Authorization", format!("Bearer {:?}", token_str))
.json(&request) .send()
.await?;
response
.json::<FcmSendResult>()
.await
.map_err(FcmError::from)?
.into()
}
fn build_url(&self) -> String {
format!(
"https://fcm.googleapis.com/v1/projects/{}/messages:send",
self.project_id
)
}
async fn get_token(&self) -> Result<String, FcmError> {
let token = self
.auth
.token(&["https://www.googleapis.com/auth/firebase.messaging"])
.await?;
token.token().map(|s| s.to_string()).ok_or_else(|| {
FcmError::AuthError("Failed to retrieve token from authenticator".to_string())
})
}
}