From 461a328915d184a680b4849b1a385185a87501b7 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 23 Jul 2026 22:31:32 +0200 Subject: [PATCH] api!: remove heartbeat push notifications Heartbeat notifications are only used on iOS for classic mail servers but both code and privacy wise (notification server sees IP addresses from those users) not something we want to support any longer (was discussed with bjoern). --- deltachat-ffi/deltachat.h | 2 - deltachat-ffi/src/lib.rs | 4 +- deltachat-jsonrpc/src/api.rs | 2 +- .../src/api/types/notify_state.rs | 4 - .../src/deltachat_rpc_client/const.py | 1 - src/accounts.rs | 4 +- src/context.rs | 3 +- src/imap.rs | 6 +- src/net/http.rs | 29 ------- src/push.rs | 82 +++---------------- 10 files changed, 18 insertions(+), 119 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index ed96d5019..086813bfe 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -640,14 +640,12 @@ char* dc_get_connectivity_html (dc_context_t* context); #define DC_PUSH_NOT_CONNECTED 0 -#define DC_PUSH_HEARTBEAT 1 #define DC_PUSH_CONNECTED 2 /** * Get the current push notification state. * One of: * - DC_PUSH_NOT_CONNECTED - * - DC_PUSH_HEARTBEAT * - DC_PUSH_CONNECTED * * @memberof dc_context_t diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index d3ff6f42e..7d30eed34 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -398,7 +398,7 @@ pub unsafe extern "C" fn dc_get_push_state(context: *const dc_context_t) -> libc return 0; } let ctx = &*context; - block_on(ctx.push_state()) as libc::c_int + ctx.push_state() as libc::c_int } fn spawn_configure(ctx: Context) { @@ -5010,7 +5010,7 @@ pub unsafe extern "C" fn dc_accounts_set_push_device_token( block_on(async move { let accounts = accounts.read().await; - if let Err(err) = accounts.set_push_device_token(&token).await { + if let Err(err) = accounts.set_push_device_token(&token) { accounts.emit_event(EventType::Error(format!( "Failed to set notify token: {err:#}." ))); diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 788a220f7..3b16483b8 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -333,7 +333,7 @@ impl CommandApi { /// Get the current push notification state. async fn get_push_state(&self, account_id: u32) -> Result { let ctx = self.get_context(account_id).await?; - Ok(ctx.push_state().await.into()) + Ok(ctx.push_state().into()) } /// Get the combined filesize of an account in bytes diff --git a/deltachat-jsonrpc/src/api/types/notify_state.rs b/deltachat-jsonrpc/src/api/types/notify_state.rs index 027f0ee23..1ede71169 100644 --- a/deltachat-jsonrpc/src/api/types/notify_state.rs +++ b/deltachat-jsonrpc/src/api/types/notify_state.rs @@ -8,9 +8,6 @@ pub enum JsonrpcNotifyState { /// Not subscribed to push notifications. NotConnected, - /// Subscribed to heartbeat push notifications. - Heartbeat, - /// Subscribed to push notifications for new messages. Connected, } @@ -19,7 +16,6 @@ impl From for JsonrpcNotifyState { fn from(state: NotifyState) -> Self { match state { NotifyState::NotConnected => Self::NotConnected, - NotifyState::Heartbeat => Self::Heartbeat, NotifyState::Connected => Self::Connected, } } diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/const.py b/deltachat-rpc-client/src/deltachat_rpc_client/const.py index 581cf56a4..10d46dbce 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/const.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/const.py @@ -250,7 +250,6 @@ class PushNotifyState(IntEnum): """Push notifications state.""" NOT_CONNECTED = 0 - HEARTBEAT = 1 CONNECTED = 2 diff --git a/src/accounts.rs b/src/accounts.rs index c825c2d15..1efaa63d1 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -534,8 +534,8 @@ impl Accounts { } /// Sets notification token for Apple Push Notification service. - pub async fn set_push_device_token(&self, token: &str) -> Result<()> { - self.push_subscriber.set_device_token(token).await; + pub fn set_push_device_token(&self, token: &str) -> Result<()> { + self.push_subscriber.set_device_token(token); Ok(()) } diff --git a/src/context.rs b/src/context.rs index e9d2f538a..230395199 100644 --- a/src/context.rs +++ b/src/context.rs @@ -293,8 +293,7 @@ pub struct InnerContext { /// because the lock is used from synchronous [`Context::emit_event`]. pub(crate) debug_logging: std::sync::RwLock>, - /// Push subscriber to store device token - /// and register for heartbeat notifications. + /// Push subscriber to store device token. pub(crate) push_subscriber: PushSubscriber, /// True if account has subscribed to push notifications via IMAP. diff --git a/src/imap.rs b/src/imap.rs index 03fd07203..fdcdb1c32 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -1474,7 +1474,7 @@ impl Session { let transport_id = self.transport_id(); - let Some(device_token) = context.push_subscriber.device_token().await else { + let Some(device_token) = context.push_subscriber.device_token() else { return Ok(()); }; @@ -1547,10 +1547,6 @@ impl Session { context.push_subscribed.store(true, Ordering::Relaxed); } - } else if !context.push_subscriber.heartbeat_subscribed().await { - let context = context.clone(); - // Subscribe for heartbeat notifications. - tokio::spawn(async move { context.push_subscriber.subscribe(&context).await }); } Ok(()) diff --git a/src/net/http.rs b/src/net/http.rs index af86dfa41..aaaa4599d 100644 --- a/src/net/http.rs +++ b/src/net/http.rs @@ -423,35 +423,6 @@ pub(crate) async fn post_empty(context: &Context, url: &str) -> Result<(String, Ok((response_text, response_status.is_success())) } -/// Posts string to the given URL. -/// -/// Returns true if successful HTTP response code was returned. -/// -/// Does not follow redirects. -#[allow(dead_code)] -pub(crate) async fn post_string(context: &Context, url: &str, body: String) -> Result { - let parsed_url = url - .parse::() - .with_context(|| format!("Failed to parse URL {url:?}"))?; - let scheme = parsed_url.scheme_str().context("URL has no scheme")?; - if scheme != "https" { - bail!("POST requests to non-HTTPS URLs are not allowed"); - } - - let mut sender = get_http_sender(context, parsed_url.clone(), true).await?; - let authority = parsed_url - .authority() - .context("URL has no authority")? - .clone(); - - let request = hyper::Request::post(parsed_url) - .header(hyper::header::HOST, authority.as_str()) - .body(body)?; - let response = sender.send_request(request).await?; - - Ok(response.status().is_success()) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/push.rs b/src/push.rs index 8c7580c85..d61522d17 100644 --- a/src/push.rs +++ b/src/push.rs @@ -13,7 +13,6 @@ use anyhow::{Context as _, Result}; use base64::Engine as _; use pgp::crypto::aead::{AeadAlgorithm, ChunkSize}; use pgp::crypto::sym::SymmetricKeyAlgorithm; -use tokio::sync::RwLock; use crate::context::Context; use crate::key::DcKey; @@ -27,13 +26,9 @@ use crate::key::DcKey; /// /// Each account (context) can then retrieve device token /// from this structure and give it to the email server. -/// If email server does not support push notifications, -/// account can call `subscribe` method -/// to register device token with the heartbeat -/// notification provider server as a fallback. #[derive(Debug, Clone, Default)] pub struct PushSubscriber { - inner: Arc>, + device_token: Arc>>, } /// The key was generated with @@ -103,8 +98,8 @@ impl PushSubscriber { /// Sets device token for Apple Push Notification service /// or Firebase Cloud Messaging. - pub(crate) async fn set_device_token(&self, token: &str) { - self.inner.write().await.device_token = Some(token.to_string()); + pub(crate) fn set_device_token(&self, token: &str) { + *self.device_token.write() = Some(token.to_string()); } /// Retrieves device token. @@ -117,59 +112,9 @@ impl PushSubscriber { /// /// IMAP loop should periodically check if device token is available /// and send the token to the email server if it supports push notifications. - pub(crate) async fn device_token(&self) -> Option { - self.inner.read().await.device_token.clone() + pub(crate) fn device_token(&self) -> Option { + self.device_token.read().clone() } - - /// Subscribes for heartbeat notifications with previously set device token. - #[cfg(target_os = "ios")] - pub(crate) async fn subscribe(&self, context: &Context) -> Result<()> { - use crate::net::http; - - let mut state = self.inner.write().await; - - if state.heartbeat_subscribed { - return Ok(()); - } - - let Some(ref token) = state.device_token else { - return Ok(()); - }; - - info!(context, "Subscribing for heartbeat notifications."); - if http::post_string( - context, - "https://notifications.delta.chat/register", - format!("{{\"token\":\"{token}\"}}"), - ) - .await? - { - info!(context, "Subscribed for heartbeat notifications."); - state.heartbeat_subscribed = true; - } - Ok(()) - } - - /// Placeholder to skip subscribing to heartbeat notifications outside iOS. - #[cfg(not(target_os = "ios"))] - pub(crate) async fn subscribe(&self, _context: &Context) -> Result<()> { - let mut state = self.inner.write().await; - state.heartbeat_subscribed = true; - Ok(()) - } - - pub(crate) async fn heartbeat_subscribed(&self) -> bool { - self.inner.read().await.heartbeat_subscribed - } -} - -#[derive(Debug, Default)] -pub(crate) struct PushSubscriberState { - /// Device token. - device_token: Option, - - /// If subscribed to heartbeat push notifications. - heartbeat_subscribed: bool, } /// Push notification state @@ -180,20 +125,15 @@ pub enum NotifyState { #[default] NotConnected = 0, - /// Subscribed to heartbeat push notifications. - Heartbeat = 1, - /// Subscribed to push notifications for new messages. Connected = 2, } impl Context { /// Returns push notification subscriber state. - pub async fn push_state(&self) -> NotifyState { + pub fn push_state(&self) -> NotifyState { if self.push_subscribed.load(Ordering::Relaxed) { NotifyState::Connected - } else if self.push_subscriber.heartbeat_subscribed().await { - NotifyState::Heartbeat } else { NotifyState::NotConnected } @@ -204,13 +144,13 @@ impl Context { mod tests { use super::*; - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_set_device_token() { + #[test] + fn test_set_device_token() { let push_subscriber = PushSubscriber::new(); - assert_eq!(push_subscriber.device_token().await, None); + assert_eq!(push_subscriber.device_token(), None); - push_subscriber.set_device_token("some-token").await; - let device_token = push_subscriber.device_token().await.unwrap(); + push_subscriber.set_device_token("some-token"); + let device_token = push_subscriber.device_token().unwrap(); assert_eq!(device_token, "some-token"); }