1#![warn(missing_docs)]
2pub use google_apis_common::{
7 GetToken,
8 auth::{GetTokenClone, NoToken},
9};
10use reqwest::{
11 Client as HttpClient, IntoUrl, RequestBuilder, Response, Url,
12 header::{self, HeaderMap, HeaderValue},
13};
14
15pub use raw::{Operation, OperationResponse};
16
17use error::operation_errors::OperationResult;
18
19pub mod error;
20mod raw;
21
22pub const FIREBASE_NOTIFICATION_URL: &str = "https://fcm.googleapis.com/fcm/notification";
24
25const FCM_DEVICE_GROUP_SCOPES: &[&str] = &["https://www.googleapis.com/auth/firebase.messaging"];
26
27#[derive(Clone)]
29pub struct FCMDeviceGroupClient {
30 url: Url,
31 client: HttpClient,
32 auth: Box<dyn GetToken + 'static>,
33}
34
35#[derive(Debug)]
37pub struct FCMDeviceGroup {
38 pub notification_key_name: String,
40 pub notification_key: String,
44}
45
46impl FCMDeviceGroupClient {
47 pub fn new(
49 sender_id: &str,
50 auth: impl GetToken + 'static,
51 ) -> Result<Self, error::FCMDeviceGroupClientCreationError> {
52 Self::with_url(FIREBASE_NOTIFICATION_URL, sender_id, auth)
53 }
54
55 pub fn with_url(
57 url: impl IntoUrl,
58 sender_id: &str,
59 auth: impl GetToken + 'static,
60 ) -> Result<Self, error::FCMDeviceGroupClientCreationError> {
61 let mut headers = HeaderMap::new();
62 headers.insert("project_id", header::HeaderValue::try_from(sender_id)?);
63 headers.insert(
64 "access_token_auth",
65 header::HeaderValue::from_static("true"),
66 );
67
68 Ok(Self {
69 url: url.into_url().unwrap(),
70 client: HttpClient::builder()
71 .default_headers(headers)
72 .connection_verbose(true)
73 .build()?,
74 auth: Box::new(auth),
75 })
76 }
77
78 pub fn with_client(
81 client: HttpClient,
82 url: impl IntoUrl,
83 auth: impl GetToken + 'static,
84 ) -> Self {
85 Self {
86 url: url.into_url().unwrap(),
87 client,
88 auth: Box::new(auth),
89 }
90 }
91
92 pub async fn apply(
94 &self,
95 operation: Operation,
96 ) -> Result<
97 OperationResponse,
98 error::FCMDeviceGroupsRequestError<error::FCMDeviceGroupsBadRequest>,
99 > {
100 let response = self.apply_raw(operation).await?;
101 error::FCMDeviceGroupsRequestError::json_response(response).await
102 }
103
104 pub async fn create_group(
106 &self,
107 notification_key_name: String,
108 registration_ids: Vec<String>,
109 ) -> OperationResult<FCMDeviceGroup, error::operation_errors::CreateGroupError> {
110 self.apply_operation(Operation::Create {
111 notification_key_name: notification_key_name.clone(),
112 registration_ids,
113 })
114 .await
115 }
116
117 pub async fn add_to_group(
119 &self,
120 group: FCMDeviceGroup,
121 registration_ids: Vec<String>,
122 ) -> OperationResult<FCMDeviceGroup, error::operation_errors::ChangeGroupMembersError> {
123 self.apply_operation(Operation::Add {
124 notification_key_name: Some(group.notification_key_name),
125 notification_key: group.notification_key,
126 registration_ids,
127 })
128 .await
129 }
130
131 pub async fn remove_from_group(
133 &self,
134 group: FCMDeviceGroup,
135 registration_ids: Vec<String>,
136 ) -> OperationResult<FCMDeviceGroup, error::operation_errors::ChangeGroupMembersError> {
137 self.apply_operation(Operation::Remove {
138 notification_key_name: Some(group.notification_key_name),
139 notification_key: group.notification_key,
140 registration_ids,
141 })
142 .await
143 }
144
145 pub async fn get_key(
147 &self,
148 notification_key_name: String,
149 ) -> OperationResult<FCMDeviceGroup, error::operation_errors::GetKeyError> {
150 let request = self
151 .client
152 .get(self.url.clone())
153 .query(&[("notification_key_name", notification_key_name.as_str())])
154 .header(
155 header::CONTENT_TYPE,
156 HeaderValue::from_static("application/json"),
157 );
158 let response = self
159 .add_token(request)
160 .await
161 .map_err(error::RawError::GetTokenError)?
162 .send()
163 .await?;
164 let response =
165 error::FCMDeviceGroupsRequestError::<error::operation_errors::GetKeyError>::json_response::<OperationResponse>(response)
166 .await?;
167 Ok(FCMDeviceGroup {
168 notification_key_name,
169 notification_key: response.notification_key,
170 })
171 }
172
173 async fn apply_raw(&self, operation: Operation) -> Result<Response, error::RawError> {
174 let request = self.client.post(self.url.clone()).json(&operation);
175
176 let request = self
177 .add_token(request)
178 .await
179 .map_err(error::RawError::GetTokenError)?;
180
181 Ok(request.send().await?)
182 }
183
184 async fn add_token(
185 &self,
186 request: RequestBuilder,
187 ) -> Result<RequestBuilder, Box<dyn std::error::Error + Send + Sync>> {
188 match self.auth.get_token(FCM_DEVICE_GROUP_SCOPES).await? {
189 Some(token) => Ok(request.bearer_auth(token)),
190 None => Ok(request),
191 }
192 }
193
194 async fn apply_operation<E: error::FCMDeviceGroupError>(
195 &self,
196 operation: Operation,
197 ) -> OperationResult<FCMDeviceGroup, E> {
198 let key_name = match &operation {
199 Operation::Create {
200 notification_key_name,
201 ..
202 } => notification_key_name.to_owned(),
203 Operation::Add {
204 notification_key_name,
205 ..
206 } => notification_key_name
207 .as_ref()
208 .expect("Applying an operation should always have a key name")
209 .to_owned(),
210 Operation::Remove {
211 notification_key_name,
212 ..
213 } => notification_key_name
214 .as_ref()
215 .expect("Applying an operation should always have a key name")
216 .to_owned(),
217 };
218 let response = self.apply_raw(operation).await?;
219 let response =
220 error::FCMDeviceGroupsRequestError::<E>::json_response::<OperationResponse>(response)
221 .await?;
222 Ok(FCMDeviceGroup {
223 notification_key_name: key_name,
224 notification_key: response.notification_key,
225 })
226 }
227}