#![warn(missing_docs)]
pub use google_apis_common::{
GetToken,
auth::{GetTokenClone, NoToken},
};
use reqwest::{
Client as HttpClient, IntoUrl, RequestBuilder, Response, Url,
header::{self, HeaderMap, HeaderValue},
};
pub use raw::{Operation, OperationResponse};
use error::operation_errors::OperationResult;
pub mod error;
mod raw;
pub const FIREBASE_NOTIFICATION_URL: &str = "https://fcm.googleapis.com/fcm/notification";
const FCM_DEVICE_GROUP_SCOPES: &[&str] = &["https://www.googleapis.com/auth/firebase.messaging"];
#[derive(Clone)]
pub struct FCMDeviceGroupClient {
url: Url,
client: HttpClient,
auth: Box<dyn GetToken + 'static>,
}
#[derive(Debug)]
pub struct FCMDeviceGroup {
pub notification_key_name: String,
pub notification_key: String,
}
impl FCMDeviceGroupClient {
pub fn new(
sender_id: &str,
auth: impl GetToken + 'static,
) -> Result<Self, error::FCMDeviceGroupClientCreationError> {
Self::with_url(FIREBASE_NOTIFICATION_URL, sender_id, auth)
}
pub fn with_url(
url: impl IntoUrl,
sender_id: &str,
auth: impl GetToken + 'static,
) -> Result<Self, error::FCMDeviceGroupClientCreationError> {
let mut headers = HeaderMap::new();
headers.insert("project_id", header::HeaderValue::try_from(sender_id)?);
headers.insert(
"access_token_auth",
header::HeaderValue::from_static("true"),
);
Ok(Self {
url: url.into_url().unwrap(),
client: HttpClient::builder()
.default_headers(headers)
.connection_verbose(true)
.build()?,
auth: Box::new(auth),
})
}
pub fn with_client(
client: HttpClient,
url: impl IntoUrl,
auth: impl GetToken + 'static,
) -> Self {
Self {
url: url.into_url().unwrap(),
client,
auth: Box::new(auth),
}
}
pub async fn apply(
&self,
operation: Operation,
) -> Result<
OperationResponse,
error::FCMDeviceGroupsRequestError<error::FCMDeviceGroupsBadRequest>,
> {
let response = self.apply_raw(operation).await?;
error::FCMDeviceGroupsRequestError::json_response(response).await
}
pub async fn create_group(
&self,
notification_key_name: String,
registration_ids: Vec<String>,
) -> OperationResult<FCMDeviceGroup, error::operation_errors::CreateGroupError> {
self.apply_operation(Operation::Create {
notification_key_name: notification_key_name.clone(),
registration_ids,
})
.await
}
pub async fn add_to_group(
&self,
group: FCMDeviceGroup,
registration_ids: Vec<String>,
) -> OperationResult<FCMDeviceGroup, error::operation_errors::ChangeGroupMembersError> {
self.apply_operation(Operation::Add {
notification_key_name: Some(group.notification_key_name),
notification_key: group.notification_key,
registration_ids,
})
.await
}
pub async fn remove_from_group(
&self,
group: FCMDeviceGroup,
registration_ids: Vec<String>,
) -> OperationResult<FCMDeviceGroup, error::operation_errors::ChangeGroupMembersError> {
self.apply_operation(Operation::Remove {
notification_key_name: Some(group.notification_key_name),
notification_key: group.notification_key,
registration_ids,
})
.await
}
pub async fn get_key(
&self,
notification_key_name: String,
) -> OperationResult<FCMDeviceGroup, error::operation_errors::GetKeyError> {
let request = self
.client
.get(self.url.clone())
.query(&[("notification_key_name", notification_key_name.as_str())])
.header(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
let response = self
.add_token(request)
.await
.map_err(error::RawError::GetTokenError)?
.send()
.await?;
let response =
error::FCMDeviceGroupsRequestError::<error::operation_errors::GetKeyError>::json_response::<OperationResponse>(response)
.await?;
Ok(FCMDeviceGroup {
notification_key_name,
notification_key: response.notification_key,
})
}
async fn apply_raw(&self, operation: Operation) -> Result<Response, error::RawError> {
let request = self.client.post(self.url.clone()).json(&operation);
let request = self
.add_token(request)
.await
.map_err(error::RawError::GetTokenError)?;
Ok(request.send().await?)
}
async fn add_token(
&self,
request: RequestBuilder,
) -> Result<RequestBuilder, Box<dyn std::error::Error + Send + Sync>> {
match self.auth.get_token(FCM_DEVICE_GROUP_SCOPES).await? {
Some(token) => Ok(request.bearer_auth(token)),
None => Ok(request),
}
}
async fn apply_operation<E: error::FCMDeviceGroupError>(
&self,
operation: Operation,
) -> OperationResult<FCMDeviceGroup, E> {
let key_name = match &operation {
Operation::Create {
notification_key_name,
..
} => notification_key_name.to_owned(),
Operation::Add {
notification_key_name,
..
} => notification_key_name
.as_ref()
.expect("Applying an operation should always have a key name")
.to_owned(),
Operation::Remove {
notification_key_name,
..
} => notification_key_name
.as_ref()
.expect("Applying an operation should always have a key name")
.to_owned(),
};
let response = self.apply_raw(operation).await?;
let response =
error::FCMDeviceGroupsRequestError::<E>::json_response::<OperationResponse>(response)
.await?;
Ok(FCMDeviceGroup {
notification_key_name: key_name,
notification_key: response.notification_key,
})
}
}