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:
@ -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")?,
|
||||
})
|
||||
}
|
145
src/helpers/firebase_notifications_helper.rs
Normal file
145
src/helpers/firebase_notifications_helper.rs
Normal 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(¬if)
|
||||
.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(())
|
||||
}
|
@ -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;
|
@ -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)?;
|
||||
|
Reference in New Issue
Block a user