1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2025-06-20 16:35:17 +00:00

Can send push notification messages through Firebase

This commit is contained in:
2021-04-15 16:35:25 +02:00
parent bd1f7ac63b
commit 42c7425aae
9 changed files with 360 additions and 14 deletions

View File

@ -23,6 +23,13 @@ pub fn get_by_origin(name: &str) -> ResultBoxError<APIClient> {
.query_row(db_to_client)
}
/// Get information about a client, based on its ID
pub fn get_by_id(id: u64) -> Res<APIClient> {
QueryInfo::new(CLIENTS_TABLE)
.cond_u64("id", id)
.query_row(db_to_client)
}
fn db_to_client(res: &database::RowResult) -> Res<APIClient> {
Ok(APIClient {
id: res.get_u64("id")?,
@ -30,6 +37,7 @@ fn db_to_client(res: &database::RowResult) -> Res<APIClient> {
domain: res.get_optional_str("domain")?,
comment: res.get_optional_str("comment")?,
default_expiration_time: res.get_u64("default_expiration_time")?,
firebase_project_name: res.get_optional_str("firebase_project_name")?,
firebase_service_account_file: res.get_optional_str("firebase_service_account_file")?,
})
}

View File

@ -0,0 +1,145 @@
//! # 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(())
}

View File

@ -19,4 +19,5 @@ pub mod requests_limit_helper;
pub mod events_helper;
pub mod calls_helper;
pub mod push_notifications_helper;
pub mod independent_push_notifications_service_helper;
pub mod independent_push_notifications_service_helper;
pub mod firebase_notifications_helper;

View File

@ -9,7 +9,7 @@ use crate::data::error::Res;
use crate::data::push_notification::PushNotification;
use crate::data::user::UserID;
use crate::data::user_token::{PushNotificationToken, UserAccessToken};
use crate::helpers::{account_helper, conversations_helper, independent_push_notifications_service_helper, user_helper};
use crate::helpers::{account_helper, conversations_helper, independent_push_notifications_service_helper, user_helper, firebase_notifications_helper};
use crate::helpers::events_helper::Event;
/// Un-register for previous push notifications service
@ -26,36 +26,47 @@ pub fn un_register_from_previous_service(client: &UserAccessToken) -> Res {
}
/// Split tokens in categories : (Independent, Firebase)
fn split_tokens(targets: Vec<UserAccessToken>) -> (Vec<String>) {
fn split_tokens(targets: Vec<UserAccessToken>) -> (Vec<String>, Vec<UserAccessToken>) {
let mut independent = vec![];
let mut firebase = vec![];
for target in targets {
match target.push_notifications_token {
PushNotificationToken::INDEPENDENT(token) => {
independent.push(token)
}
PushNotificationToken::FIREBASE(_) => {
firebase.push(target);
}
_ => {}
}
}
(independent)
(independent, firebase)
}
/// Push a notification to specific tokens
fn push_notification(n: &PushNotification, targets: Vec<UserAccessToken>) -> Res {
let independents = split_tokens(targets);
let (independents, firebase) = split_tokens(targets);
// Push independent notifications
if !independents.is_empty() {
independent_push_notifications_service_helper::push_notifications(n, independents)?;
}
// Push Firebase notifications
if !firebase.is_empty() {
firebase_notifications_helper::push_notifications(n, firebase)?;
}
Ok(())
}
/// Cancel a notification for specific tokens (optional)
fn cancel_notification(id: String, targets: Vec<UserAccessToken>) -> Res {
let independents = split_tokens(targets);
let (independents, _) = split_tokens(targets);
if !independents.is_empty() {
independent_push_notifications_service_helper::cancel_notifications(id, independents)?;