diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index e85e0222..648b65de 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -47,8 +47,18 @@ private_key = "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJSUWpyTWxLR public_key = "BGcvgR-i2z4IQ5Mw841vJvkLjt8wY-FjmWrw83jOLCY52qcGZS0OF7nfLzuYbjsQISwVO2HXrmf18gLWVX3Kwfw=" [api.fcm] -# Google Firebase Cloud Messaging API key for sending notifications -api_key = "" +# Google Firebase Cloud Messaging Service Account Key +# Obtained from the cloud messaging console +key_type = "" +project_id = "" +private_key_id = "" +private_key = "" +client_email = "" +client_id = "" +auth_uri = "" +token_uri = "" +auth_provider_x509_cert_url = "" +client_x509_cert_url = "" [api.apn] # Apple Push Notifications keys for sending notifications diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 5877c795..7812ac7b 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -74,7 +74,16 @@ pub struct ApiVapid { #[derive(Deserialize, Debug, Clone)] pub struct ApiFcm { - pub api_key: String, + pub key_type: String, + pub project_id: String, + pub private_key_id: String, + pub private_key: String, + pub client_email: String, + pub client_id: String, + pub auth_uri: String, + pub token_uri: String, + pub auth_provider_x509_cert_url: String, + pub client_x509_cert_url: String, } #[derive(Deserialize, Debug, Clone)] diff --git a/crates/core/database/Cargo.toml b/crates/core/database/Cargo.toml index 5ba88752..a97e4e06 100644 --- a/crates/core/database/Cargo.toml +++ b/crates/core/database/Cargo.toml @@ -85,11 +85,9 @@ revolt_okapi = { version = "0.9.1", optional = true } revolt_rocket_okapi = { version = "0.9.1", optional = true } # Notifications -fcm = "0.9.2" +fcm_v1 = "0.3.0" web-push = "0.10.0" -revolt_a2 = { version = "0.10", default-features = false, features = [ - "ring", -] } +revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] } # Authifier authifier = { version = "1.0.8" } diff --git a/crates/core/database/src/tasks/web_push.rs b/crates/core/database/src/tasks/web_push.rs index 7e24e2af..d81250f0 100644 --- a/crates/core/database/src/tasks/web_push.rs +++ b/crates/core/database/src/tasks/web_push.rs @@ -1,4 +1,7 @@ -use std::collections::HashSet; +use std::{ + collections::{HashMap, HashSet}, + time::Duration, +}; use authifier::Database; use base64::{ @@ -6,6 +9,7 @@ use base64::{ Engine as _, }; use deadqueue::limited::Queue; +use fcm_v1::auth::{Authenticator, ServiceAccountKey}; use once_cell::sync::Lazy; use revolt_config::config; use revolt_models::v0::PushNotification; @@ -54,10 +58,28 @@ pub async fn worker(db: Database) { let config = config().await; let web_push_client = IsahcWebPushClient::new().unwrap(); - let fcm_client = if config.api.fcm.api_key.is_empty() { + let fcm_client = if config.api.fcm.key_type.is_empty() { None } else { - Some(fcm::Client::new()) + Some(fcm_v1::Client::new( + Authenticator::service_account::<&str>(ServiceAccountKey { + key_type: Some(config.api.fcm.key_type), + project_id: Some(config.api.fcm.project_id.clone()), + private_key_id: Some(config.api.fcm.private_key_id), + private_key: config.api.fcm.private_key, + client_email: config.api.fcm.client_email, + client_id: Some(config.api.fcm.client_id), + auth_uri: Some(config.api.fcm.auth_uri), + token_uri: config.api.fcm.token_uri, + auth_provider_x509_cert_url: Some(config.api.fcm.auth_provider_x509_cert_url), + client_x509_cert_url: Some(config.api.fcm.client_x509_cert_url), + }) + .await + .unwrap(), + config.api.fcm.project_id, + false, + Duration::from_secs(5), + )) }; let web_push_private_key = engine::general_purpose::URL_SAFE_NO_PAD @@ -76,27 +98,38 @@ pub async fn worker(db: Database) { let PushNotification { author, icon, - image: _, + image, body, - tag, - timestamp: _, + tag: _, + timestamp, url: _, - message: _, + message, } = &task.payload; - let mut notification = fcm::NotificationBuilder::new(); - notification.title(author); - notification.icon(icon); - notification.body(body); - notification.tag(tag); - // TODO: expand support for fields - let notification = notification.finalize(); + let message = fcm_v1::message::Message { + token: Some(sub.auth), + data: Some(HashMap::from([ + ( + "author".to_owned(), + serde_json::Value::String(author.clone()), + ), + ("icon".to_owned(), serde_json::Value::String(icon.clone())), + ( + "image".to_owned(), + if let Some(image) = image { + serde_json::Value::String(image.clone()) + } else { + serde_json::Value::Null + }, + ), + ("body".to_owned(), serde_json::Value::String(body.clone())), + ("timestamp".to_owned(), json!(timestamp)), + ("message".to_owned(), json!(&message)), + ])), + ..Default::default() + }; - let mut message_builder = - fcm::MessageBuilder::new(&config.api.fcm.api_key, &sub.auth); - message_builder.notification(notification); - - if let Err(err) = client.send(message_builder.finalize()).await { + if let Err(err) = client.send(&message).await { error!("Failed to send FCM notification! {:?}", err); } else { info!("Sent FCM notification to {:?}.", session.id);