diff --git a/services/tunnelbroker/src/notifs/fcm/error.rs b/services/tunnelbroker/src/notifs/fcm/error.rs --- a/services/tunnelbroker/src/notifs/fcm/error.rs +++ b/services/tunnelbroker/src/notifs/fcm/error.rs @@ -1,4 +1,4 @@ -use crate::notifs::fcm::response::FCMError; +use crate::notifs::fcm::response::FCMErrorResponse; use derive_more::{Display, Error, From}; #[derive(Debug, From, Display, Error)] @@ -8,7 +8,7 @@ InvalidHeaderValue(reqwest::header::InvalidHeaderValue), SerdeJson(serde_json::Error), FCMTokenNotInitialized, - FCMError(FCMError), + FCMError(FCMErrorResponse), } impl From for Error { diff --git a/services/tunnelbroker/src/notifs/fcm/mod.rs b/services/tunnelbroker/src/notifs/fcm/mod.rs --- a/services/tunnelbroker/src/notifs/fcm/mod.rs +++ b/services/tunnelbroker/src/notifs/fcm/mod.rs @@ -1,6 +1,12 @@ use crate::notifs::fcm::config::FCMConfig; +use crate::notifs::fcm::error::Error::FCMError; +use crate::notifs::fcm::firebase_message::{FCMMessage, FCMMessageWrapper}; +use crate::notifs::fcm::response::FCMErrorResponse; use crate::notifs::fcm::token::FCMToken; +use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; +use reqwest::StatusCode; use std::time::Duration; +use tracing::{debug, error}; pub mod config; mod error; @@ -30,4 +36,53 @@ token, }) } + + pub async fn send(&self, message: FCMMessage) -> Result<(), error::Error> { + let token = message.token.clone(); + debug!("Sending FCM notif to {}", token); + + let mut headers = HeaderMap::new(); + headers.insert( + reqwest::header::CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ); + + let bearer = self.token.get_auth_bearer().await?; + headers.insert(AUTHORIZATION, HeaderValue::from_str(&bearer)?); + + let url = format!( + "https://fcm.googleapis.com/v1/projects/{}/messages:send", + self.config.project_id + ); + + let msg_wrapper = FCMMessageWrapper { message }; + let payload = serde_json::to_string(&msg_wrapper).unwrap(); + + let response = self + .http_client + .post(&url) + .headers(headers) + .body(payload) + .send() + .await?; + + match response.status() { + StatusCode::OK => { + debug!("Successfully sent FCM notif to {}", token); + Ok(()) + } + error_status => { + let body = response + .text() + .await + .unwrap_or_else(|error| format!("Error occurred: {}", error)); + error!( + "Failed sending FCM notification to: {}. Status: {}. Body: {}", + token, error_status, body + ); + let fcm_error = FCMErrorResponse::from_status(error_status, body); + Err(FCMError(fcm_error)) + } + } + } } diff --git a/services/tunnelbroker/src/notifs/fcm/response.rs b/services/tunnelbroker/src/notifs/fcm/response.rs --- a/services/tunnelbroker/src/notifs/fcm/response.rs +++ b/services/tunnelbroker/src/notifs/fcm/response.rs @@ -1,11 +1,12 @@ use derive_more::{Display, Error}; +use reqwest::StatusCode; #[derive(PartialEq, Debug, Clone, Display, Error)] pub struct InvalidArgumentError { pub details: String, } #[derive(PartialEq, Debug, Display, Error)] -pub enum FCMError { +pub enum FCMErrorResponse { /// No more information is available about this error. UnspecifiedError, @@ -42,3 +43,21 @@ /// APNs certificate or web push auth key was invalid or missing. ThirdPartyAuthError, } +impl FCMErrorResponse { + pub fn from_status(status: StatusCode, body: String) -> Self { + match status { + StatusCode::BAD_REQUEST => { + FCMErrorResponse::InvalidArgument(InvalidArgumentError { + details: body, + }) + } + StatusCode::NOT_FOUND => FCMErrorResponse::Unregistered, + StatusCode::FORBIDDEN => FCMErrorResponse::SenderIdMismatch, + StatusCode::TOO_MANY_REQUESTS => FCMErrorResponse::QuotaExceeded, + StatusCode::SERVICE_UNAVAILABLE => FCMErrorResponse::Unavailable, + StatusCode::INTERNAL_SERVER_ERROR => FCMErrorResponse::Internal, + StatusCode::UNAUTHORIZED => FCMErrorResponse::ThirdPartyAuthError, + _ => FCMErrorResponse::UnspecifiedError, + } + } +}