1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2025-04-21 03:50:55 +00:00
comunicapiv3/src/helpers/firebase_notifications_helper.rs

145 lines
4.3 KiB
Rust

//! # Firebase notifications helper
//!
//! Helper used to send notifications through the Firebase Cloud Messaging services
//!
//! @author Pierre Hubert
use std::collections::HashMap;
use reqwest::blocking::Client;
use reqwest::header::{HeaderMap, HeaderValue};
use serde::Serialize;
use crate::constants::FIREBASE_PUSH_MESSAGE_URL;
use crate::data::api_client::APIClient;
use crate::data::error::{ExecError, Res};
use crate::data::push_notification::PushNotification;
use crate::data::user_token::{PushNotificationToken, UserAccessToken};
use crate::helpers::api_helper;
struct FirebaseClientIdentifier {
project_name: String,
authorization_token: String,
}
#[derive(Serialize)]
struct FirebaseNotification {
title: String,
body: String,
image: Option<String>,
}
#[derive(Serialize)]
struct FirebaseAndroidNotification {
tag: String,
}
#[derive(Serialize)]
struct FirebaseAndroid {
notification: FirebaseAndroidNotification,
ttl: Option<String>,
}
#[derive(Serialize)]
struct FirebaseMessage {
token: String,
notification: FirebaseNotification,
android: FirebaseAndroid,
}
#[derive(Serialize)]
struct FirebaseNotificationRequest {
validate_only: bool,
message: FirebaseMessage,
}
/// Get short-lived authorization token for a specific client
fn get_credentials(client: &APIClient) -> Res<String> {
let service_account_file = match &client.firebase_service_account_file {
Some(file) => file,
None => {
return Err(ExecError::boxed_string(format!("No service account file for client {}!", client.name)));
}
};
let token = gouth::Builder::new()
.scopes(&["https://www.googleapis.com/auth/firebase.messaging"])
.json(service_account_file)
.build()?
.header_value()?
.to_string();
Ok(token)
}
/// Send a single notification through Firebase service
fn send_notification(n: &PushNotification, client_token: &str, access: &FirebaseClientIdentifier) -> Res {
let notif = FirebaseNotificationRequest {
validate_only: false,
message: FirebaseMessage {
token: client_token.to_string(),
notification: FirebaseNotification {
title: n.title.to_string(),
body: n.body.to_string(),
image: n.image.clone(),
},
android: FirebaseAndroid {
notification: FirebaseAndroidNotification {
tag: n.id.to_string(),
},
ttl: n.timeout.map(|t| format!("{}s", t)),
},
},
};
let mut headers = HeaderMap::new();
headers.insert("Authorization", HeaderValue::from_str(&access.authorization_token)?);
let client = Client::builder().default_headers(headers).build()?;
client
.post(&FIREBASE_PUSH_MESSAGE_URL.replace("{PROJECT_ID}", &access.project_name))
.json(&notif)
.send()?;
Ok(())
}
/// Send a notification
pub fn push_notifications(n: &PushNotification, targets: Vec<UserAccessToken>) -> Res {
let mut tokens_cache: HashMap<u64, FirebaseClientIdentifier> = HashMap::new();
for target in targets {
// Get an access token if required
if !tokens_cache.contains_key(&target.client_id) {
let client = api_helper::get_by_id(target.client_id)?;
tokens_cache.insert(target.client_id, FirebaseClientIdentifier {
authorization_token: get_credentials(&client)?,
project_name: client.firebase_project_name
.ok_or(ExecError::boxed_new("Missing firebase project name!"))?,
});
}
let authorization_token = match tokens_cache.get(&target.client_id) {
None => {
return Err(ExecError::boxed_new("Should have a Firebase token now!!!"));
}
Some(token) => token
};
let client_token = match &target.push_notifications_token {
PushNotificationToken::FIREBASE(token) => token,
_ => {
return Err(ExecError::boxed_new("Invalid token!"));
}
};
if let Err(e) = send_notification(n, client_token, authorization_token) {
eprintln!("Failed to send a push notification to a device! Error: {}", e);
}
}
Ok(())
}