From f6f9cdb1557b86fe69e9ac4b2ab74c3ef4660e5e Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Fri, 17 Apr 2026 13:07:20 -0400 Subject: [PATCH 1/3] don't clear badge count on readmessage for multi account --- go/bind/keybase.go | 13 ++++++++++-- go/bind/notifications.go | 5 +++++ .../io/keybase/ossifrage/KBPushNotifier.kt | 8 +++++++- .../KeybasePushNotificationListenerService.kt | 12 ++++++++--- .../platform-specific/push.native.tsx | 2 ++ shared/constants/types/push.tsx | 1 + shared/ios/Keybase/Pusher.swift | 20 +++++++++++++++---- 7 files changed, 51 insertions(+), 10 deletions(-) diff --git a/go/bind/keybase.go b/go/bind/keybase.go index 95a70e9fef0d..029b5ab3fd9c 100644 --- a/go/bind/keybase.go +++ b/go/bind/keybase.go @@ -85,8 +85,17 @@ func log(format string, args ...interface{}) { } } +// CurrentUID returns the UID of the currently active account, or empty string if not logged in. +// Used by the Android push listener to gate notification actions on the active account. +func CurrentUID() string { + if kbCtx == nil { + return "" + } + return kbCtx.Env.GetUID().String() +} + type PushNotifier interface { - LocalNotification(ident string, title string, msg string, badgeCount int, soundName string, convID string, typ string) + LocalNotification(ident string, title string, msg string, badgeCount int, soundName string, convID string, typ string, uid string) DisplayChatNotification(notification *ChatNotification) } @@ -734,7 +743,7 @@ func pushPendingMessageFailure(obrs []chat1.OutboxRecord, pusher PushNotifier) { kbCtx.Log.Debug("pushPendingMessageFailure: pushing convID: %s", obr.ConvID) pusher.LocalNotification("failedpending", "", "Heads up! Your message hasn't sent yet, tap here to retry.", - -1, "default", obr.ConvID.String(), "chat.failedpending") + -1, "default", obr.ConvID.String(), "chat.failedpending", "") return } } diff --git a/go/bind/notifications.go b/go/bind/notifications.go index c04fc25cd7a7..55f3a9dfb8e6 100644 --- a/go/bind/notifications.go +++ b/go/bind/notifications.go @@ -98,6 +98,10 @@ type ChatNotification struct { BadgeCount int // Title is the notification title, e.g. "username@keybase" Title string + // Uid is the UID of the account this notification belongs to. + // Included in the local notification's userInfo so that a notification + // tap can switch to the correct account if a different one is active. + Uid string } func HandlePostTextReply(strConvID, tlfName string, intMessageID int, body string) (err error) { @@ -224,6 +228,7 @@ func HandleBackgroundNotification(strConvID, body, serverMessageBody, sender str SoundName: soundName, BadgeCount: badgeCount, Title: title, + Uid: uid.String(), } kbCtx.Log.CDebugf(ctx, "HandleBackgroundNotification: title=%s", chatNotification.Title) diff --git a/shared/android/app/src/main/java/io/keybase/ossifrage/KBPushNotifier.kt b/shared/android/app/src/main/java/io/keybase/ossifrage/KBPushNotifier.kt index 5648cee9a421..78ab049ee8b7 100644 --- a/shared/android/app/src/main/java/io/keybase/ossifrage/KBPushNotifier.kt +++ b/shared/android/app/src/main/java/io/keybase/ossifrage/KBPushNotifier.kt @@ -119,6 +119,9 @@ class KBPushNotifier internal constructor(private val context: Context, private bundle.putBoolean("userInteraction", true) bundle.putString("type", "chat.newmessage") bundle.putString("convID", chatNotification.convID) + if (chatNotification.uid.isNotEmpty()) { + bundle.putString("uid", chatNotification.uid) + } val pending_intent = buildPendingIntent(bundle) val convData = ConvData(chatNotification.convID, chatNotification.tlfName ?: "", chatNotification.message.id) val builder = NotificationCompat.Builder(context, KeybasePushNotificationListenerService.CHAT_CHANNEL_ID) @@ -236,7 +239,10 @@ class KBPushNotifier internal constructor(private val context: Context, private } override fun localNotification(ident: String, title: String, msg: String, badgeCount: Long, soundName: String, convID: String, - typ: String) { + typ: String, uid: String) { + if (uid.isNotEmpty()) { + bundle.putString("uid", uid) + } genericNotification(ident, title, msg, bundle, KeybasePushNotificationListenerService.GENERAL_CHANNEL_ID) } diff --git a/shared/android/app/src/main/java/io/keybase/ossifrage/KeybasePushNotificationListenerService.kt b/shared/android/app/src/main/java/io/keybase/ossifrage/KeybasePushNotificationListenerService.kt index 0b483f01682b..570e85e60d52 100644 --- a/shared/android/app/src/main/java/io/keybase/ossifrage/KeybasePushNotificationListenerService.kt +++ b/shared/android/app/src/main/java/io/keybase/ossifrage/KeybasePushNotificationListenerService.kt @@ -205,6 +205,7 @@ class KeybasePushNotificationListenerService : FirebaseMessagingService() { chatNotif.isGroupConversation = false chatNotif.tlfName = "" chatNotif.title = bundle.getString("title", "") + chatNotif.uid = targetUID notifier.displayChatNotification(chatNotif) seenChatNotifications.add(n.convID + n.messageId) @@ -244,13 +245,18 @@ class KeybasePushNotificationListenerService : FirebaseMessagingService() { "chat.readmessage" -> { val convID = bundle.getString("c") + val targetUID = bundle.getString("i", "") // Clear the cache of msgs for this conv id if (msgCache.containsKey(convID)) { msgCache[convID] = SmallMsgRingBuffer() } - // Cancel any push notifications. - val notificationManager = NotificationManagerCompat.from(applicationContext) - notificationManager.cancelAll() + // Only cancel notifications if this read receipt is for the active + // account. A receipt from another logged-in account must not clear + // the active account's notification tray. + if (targetUID.isEmpty() || targetUID == Keybase.currentUID()) { + val notificationManager = NotificationManagerCompat.from(applicationContext) + notificationManager.cancelAll() + } val emitBundle = bundle.clone() as Bundle KbModule.emitPushNotification(emitBundle) } diff --git a/shared/constants/platform-specific/push.native.tsx b/shared/constants/platform-specific/push.native.tsx index d06916db43c6..ec4087ffca15 100644 --- a/shared/constants/platform-specific/push.native.tsx +++ b/shared/constants/platform-specific/push.native.tsx @@ -19,6 +19,7 @@ type DataCommon = { type DataReadMessage = DataCommon & { type: 'chat.readmessage' b: string | number + i?: string } type DataNewMessage = DataCommon & { type: 'chat.newmessage' @@ -98,6 +99,7 @@ const normalizePush = (_n?: object): T.Push.PushNotification | undefined => { const badges = typeof data.b === 'string' ? parseInt(data.b) : data.b return { badges, + forUid: data.i, type: 'chat.readmessage', } as const } diff --git a/shared/constants/types/push.tsx b/shared/constants/types/push.tsx index db26c6a798df..550954367593 100644 --- a/shared/constants/types/push.tsx +++ b/shared/constants/types/push.tsx @@ -6,6 +6,7 @@ export type TokenType = 'apple' | 'appledev' | 'androidplay' export type PushNotification = | { badges: number + forUid?: string type: 'chat.readmessage' } | { diff --git a/shared/ios/Keybase/Pusher.swift b/shared/ios/Keybase/Pusher.swift index d6c6b84ff37d..85000a1dc434 100644 --- a/shared/ios/Keybase/Pusher.swift +++ b/shared/ios/Keybase/Pusher.swift @@ -5,7 +5,7 @@ import UserNotifications class PushNotifier: NSObject, Keybasego.KeybasePushNotifierProtocol { func localNotification( _ ident: String?, title: String?, msg: String?, badgeCount: Int, soundName: String?, - convID: String?, typ: String? + convID: String?, typ: String?, uid: String? ) { let content = UNMutableNotificationContent() if let soundName = soundName { @@ -14,7 +14,11 @@ class PushNotifier: NSObject, Keybasego.KeybasePushNotifierProtocol { content.badge = (badgeCount >= 0) ? NSNumber(value: badgeCount) : nil content.title = title ?? "" content.body = msg ?? "" - content.userInfo = ["convID": convID ?? "", "type": typ ?? ""] + var userInfo: [String: Any] = ["convID": convID ?? "", "type": typ ?? ""] + if let uid = uid, !uid.isEmpty { + userInfo["uid"] = uid + } + content.userInfo = userInfo let request = UNNotificationRequest( identifier: ident ?? UUID().uuidString, content: content, trigger: nil) UNUserNotificationCenter.current().add(request) { error in @@ -41,8 +45,16 @@ class PushNotifier: NSObject, Keybasego.KeybasePushNotifierProtocol { } let title = notification.title NSLog("PushNotifier display: title=%@", title ?? "") + + let soundName: String? = notification.soundName?.isEmpty == false ? notification.soundName : nil + let uid: String? = notification.uid.isEmpty ? nil : notification.uid localNotification( - ident, title: title, msg: msg, badgeCount: notification.badgeCount, - soundName: notification.soundName, convID: notification.convID, typ: "chat.newmessage") + ident, title: title, msg: msg, + badgeCount: notification.badgeCount, + soundName: soundName, + convID: notification.convID, + typ: "chat.newmessage", + uid: uid + ) } } From 2f4517a2a7f104e55a0a408039f950fd3d2b2ce5 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Fri, 17 Apr 2026 14:07:14 -0400 Subject: [PATCH 2/3] x --- shared/ios/Keybase/Pusher.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/shared/ios/Keybase/Pusher.swift b/shared/ios/Keybase/Pusher.swift index 85000a1dc434..b75594cac412 100644 --- a/shared/ios/Keybase/Pusher.swift +++ b/shared/ios/Keybase/Pusher.swift @@ -14,11 +14,7 @@ class PushNotifier: NSObject, Keybasego.KeybasePushNotifierProtocol { content.badge = (badgeCount >= 0) ? NSNumber(value: badgeCount) : nil content.title = title ?? "" content.body = msg ?? "" - var userInfo: [String: Any] = ["convID": convID ?? "", "type": typ ?? ""] - if let uid = uid, !uid.isEmpty { - userInfo["uid"] = uid - } - content.userInfo = userInfo + content.userInfo = ["convID": convID ?? "", "type": typ ?? "", "uid": uid ?? ""] let request = UNNotificationRequest( identifier: ident ?? UUID().uuidString, content: content, trigger: nil) UNUserNotificationCenter.current().add(request) { error in From 0595e96414cf167d1708af83ba2d3639a1e4c6a4 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Fri, 17 Apr 2026 14:18:05 -0400 Subject: [PATCH 3/3] x --- shared/ios/Keybase/Pusher.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/ios/Keybase/Pusher.swift b/shared/ios/Keybase/Pusher.swift index b75594cac412..25273de4c19c 100644 --- a/shared/ios/Keybase/Pusher.swift +++ b/shared/ios/Keybase/Pusher.swift @@ -40,9 +40,9 @@ class PushNotifier: NSObject, Keybasego.KeybasePushNotifierProtocol { msg = message.serverMessage } let title = notification.title - NSLog("PushNotifier display: title=%@", title ?? "") + NSLog("PushNotifier display: title=%@", title) - let soundName: String? = notification.soundName?.isEmpty == false ? notification.soundName : nil + let soundName: String? = notification.soundName.isEmpty ? nil : notification.soundName let uid: String? = notification.uid.isEmpty ? nil : notification.uid localNotification( ident, title: title, msg: msg,