From e0fcee240a373fe01e188233324ed2e2516b3444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cyrille=20B=C3=A9raud?= Date: Fri, 12 Jun 2026 08:22:02 -0400 Subject: [PATCH] proxy server: collapse low-priority push notifications When a device is unreachable (deep doze, offline), FCM queues non-collapsible messages individually, with a hard limit of 100 pending messages per device. A Jami device that is a member of a few active swarms receives a sustained stream of connection-churn and expiration notifications (observed: 27-44/min, ~12k/day), so a phone suspended for a few tens of minutes is enough to overflow the queue. Once full, FCM silently drops messages, including the high-priority notification announcing an incoming call: the callee never wakes up and the caller gets a timeout after the ICE window (observed in production, with the call notification never delivered while pushes sent after the device woke up arrived in under a second). Mark Android notifications as collapsible (collapse_key) when they are neither high-priority (calls, messages, invitations - as set by the client through Value::priority) nor resubscribe requests. While collapsed messages are pending, FCM only keeps the last one per key, so routine sync noise can no longer evict actionable notifications. A single constant key is used instead of one key per DHT listen key: FCM only honors 4 distinct collapse keys per device, and the Android client reacts to any low-priority notification the same way (wake up and resynchronize against the proxy), so finer granularity would not change client behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/dht_proxy_server.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/dht_proxy_server.cpp b/src/dht_proxy_server.cpp index ce18f3a1..fd670090 100644 --- a/src/dht_proxy_server.cpp +++ b/src/dht_proxy_server.cpp @@ -1267,6 +1267,21 @@ DhtProxyServer::sendPushNotification( Json::Value androidConfig(Json::objectValue); androidConfig["priority"] = priority; androidConfig["ttl"] = "86400s"; // time to live = 24 hours + // Make routine sync notifications (connection churn, value + // expirations) collapsible: FCM keeps only the last pending + // message per collapse key when the device is unreachable + // (dozing/offline), while non-collapsible messages pile up in + // a 100-message queue. When that queue overflows, FCM silently + // drops messages - including the high-priority ones that + // announce incoming calls. A single constant key is used + // because FCM only honors 4 distinct collapse keys per device + // and the Android client reacts to any of these notifications + // the same way: wake up and resynchronize against the proxy. + // High-priority notifications (calls, messages, invites - see + // Value::priority set by the client) and resubscribe requests + // remain non-collapsible so none of them can be overwritten. + if (!highPriority && !isResubscribe) + androidConfig["collapse_key"] = "sync"; notification["android"] = std::move(androidConfig); } else { notification["priority"] = priority;