diff --git a/Cargo.lock b/Cargo.lock index dd2d806..e731790 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,14 +391,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.3", + "windows-link", ] [[package]] @@ -1685,6 +1685,7 @@ dependencies = [ "anyhow", "axum", "base64 0.22.1", + "chrono", "femme", "humantime", "log", @@ -3446,6 +3447,12 @@ dependencies = [ "windows-targets 0.52.3", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index adc44d2..8e63c6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ a2 = { git = "https://github.com/WalletConnect/a2/", branch = "master" } anyhow = "1.0.32" axum = "0.7.5" base64 = "0.22.1" +chrono = { version = "0.4.41", default-features = false } femme = "2.1.0" humantime = "2.0.1" log = "0.4.11" diff --git a/src/metrics.rs b/src/metrics.rs index 588dbf2..c1f82e1 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -27,6 +27,9 @@ pub struct Metrics { /// Number of successfully sent visible FCM notifications. pub fcm_notifications_total: Counter, + /// Number of successfully sent visible UBports notifications. + pub ubports_notifications_total: Counter, + /// Number of successfully sent heartbeat notifications. pub heartbeat_notifications_total: Counter, @@ -58,6 +61,13 @@ impl Metrics { fcm_notifications_total.clone(), ); + let ubports_notifications_total = Counter::default(); + registry.register( + "ubports_notifications", + "Number of UBports notifications", + ubports_notifications_total.clone(), + ); + let heartbeat_notifications_total = Counter::default(); registry.register( "heartbeat_notifications", @@ -88,8 +98,9 @@ impl Metrics { Self { registry, - fcm_notifications_total, direct_notifications_total, + fcm_notifications_total, + ubports_notifications_total, heartbeat_notifications_total, heartbeat_registrations_total, heartbeat_tokens, diff --git a/src/notifier.rs b/src/notifier.rs index 1404304..2a67f07 100644 --- a/src/notifier.rs +++ b/src/notifier.rs @@ -85,7 +85,7 @@ async fn wakeup( let device_token: NotificationToken = key_device_token.as_str().parse()?; let (client, device_token) = match device_token { - NotificationToken::Fcm { .. } => { + NotificationToken::Fcm { .. } | NotificationToken::UBports(..) => { // Only APNS tokens can be registered for periodic notifications. info!("Removing FCM token {key_device_token}"); schedule diff --git a/src/server.rs b/src/server.rs index fa77827..d328bc5 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,6 +6,7 @@ use anyhow::{bail, Error, Result}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::routing::{get, post}; +use chrono::{Local, TimeDelta}; use log::*; use serde::Deserialize; use std::str::FromStr; @@ -76,6 +77,9 @@ async fn register_device( } pub(crate) enum NotificationToken { + /// Ubuntu touch app + UBports(String), + /// Android App. Fcm { /// Package name such as `chat.delta`. @@ -105,6 +109,8 @@ impl FromStr for NotificationToken { } else { bail!("Invalid FCM token"); } + } else if let Some(s) = s.strip_prefix("ubports-") { + Ok(Self::UBports(s.to_string())) } else if let Some(token) = s.strip_prefix("sandbox:") { Ok(Self::ApnsSandbox(token.to_string())) } else { @@ -113,6 +119,49 @@ impl FromStr for NotificationToken { } } +/// Notify the UBports push server +/// +/// API documentation is available at +/// +async fn notify_ubports( + client: &reqwest::Client, + token: &str, + metrics: &Metrics, +) -> Result { + if !token + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ':' || c == '-') + { + return Ok(StatusCode::GONE); + } + + let url = "https://push.ubports.com/notify"; + let expire_on = (Local::now() + TimeDelta::weeks(1)).to_rfc3339(); + let body = format!( + r#"{{"expire_on":"{expire_on}","appid":"deltatouch.lotharketterer_deltatouch","token":"{token}","data":{{"notification":{{"tag":"sent_by_chatmail_server","card":{{"popup":true,"persist":true,"summary":"New message","body":"You have a new message"}},"sound":true,"vibrate":{{"pattern":[200],"duration":200,"repeat":1}} }},"sent-by":"Chatmail Server"}} }}"# + ); + let res = client + .post(url) + .body(body.clone()) + .header("Content-Type", "application/json") + .send() + .await?; + let status = res.status(); + if status.is_client_error() { + warn!("Failed to deliver UBports notification to {token}"); + warn!("BODY: {body:?}"); + warn!("RES: {res:?}"); + return Ok(StatusCode::GONE); + } + if status.is_server_error() { + warn!("Internal server error while attempting to deliver UBports notification to {token}"); + return Ok(StatusCode::INTERNAL_SERVER_ERROR); + } + info!("Delivered notification to UBports token {token}"); + metrics.ubports_notifications_total.inc(); + Ok(StatusCode::OK) +} + /// Notifies a single FCM token. /// /// API documentation is available at @@ -247,6 +296,11 @@ async fn notify_device( let device_token: NotificationToken = device_token.as_str().parse()?; let status_code = match device_token { + NotificationToken::UBports(token) => { + let client = state.fcm_client().clone(); + let metrics = state.metrics(); + notify_ubports(&client, &token, metrics).await? + } NotificationToken::Fcm { package_name, token,