diff --git a/go/bind/notifications.go b/go/bind/notifications.go index 3e1a9ddbbc2..4482a7ca596 100644 --- a/go/bind/notifications.go +++ b/go/bind/notifications.go @@ -6,8 +6,11 @@ import ( "fmt" "regexp" "runtime" + "strconv" + "sync" "time" + lru "github.com/hashicorp/golang-lru" "github.com/keybase/client/go/chat" "github.com/keybase/client/go/chat/globals" "github.com/keybase/client/go/chat/storage" @@ -20,6 +23,21 @@ import ( "github.com/kyokomi/emoji" ) +const seenNotificationsCacheSize = 100 + +var ( + seenNotificationsMtx sync.Mutex + seenNotifications *lru.Cache + seenNotificationsOnce sync.Once +) + +func getSeenNotificationsCache() *lru.Cache { + seenNotificationsOnce.Do(func() { + seenNotifications, _ = lru.New(seenNotificationsCacheSize) + }) + return seenNotifications +} + type Person struct { KeybaseUsername string KeybaseAvatar string @@ -106,6 +124,20 @@ func HandleBackgroundNotification(strConvID, body, serverMessageBody, sender str return libkb.LoginRequiredError{} } mp := chat.NewMobilePush(gc) + // Dedupe by convID||msgID + dupKey := strConvID + "||" + strconv.Itoa(intMessageID) + // Check if we've already processed this notification but without + // serializing the whole function. We check the map again while holding + // a lock before anything is displayed. + if _, ok := getSeenNotificationsCache().Get(dupKey); ok { + // Cancel any duplicate visible notifications + if len(pushID) > 0 { + mp.AckNotificationSuccess(ctx, []string{pushID}) + } + kbCtx.Log.CDebugf(ctx, "HandleBackgroundNotification: duplicate notification convID=%s msgID=%d", strConvID, intMessageID) + // Return nil (not an error) so Android does not treat this as failure and show a fallback notification. + return nil + } uid := gregor1.UID(kbCtx.Env.GetUID().ToBytes()) convID, err := chat1.MakeConvID(strConvID) if err != nil { @@ -195,7 +227,20 @@ func HandleBackgroundNotification(strConvID, body, serverMessageBody, sender str // only display and ack this notification if we actually have something to display if pusher != nil && (len(chatNotification.Message.Plaintext) > 0 || len(chatNotification.Message.ServerMessage) > 0) { + // Lock and check if we've already processed this notification. + seenNotificationsMtx.Lock() + defer seenNotificationsMtx.Unlock() + if _, ok := getSeenNotificationsCache().Get(dupKey); ok { + // Cancel any duplicate visible notifications + if len(pushID) > 0 { + mp.AckNotificationSuccess(ctx, []string{pushID}) + } + kbCtx.Log.CDebugf(ctx, "HandleBackgroundNotification: duplicate notification convID=%s msgID=%d", strConvID, intMessageID) + // Return nil (not an error) so Android does not treat this as failure and show a fallback notification. + return nil + } pusher.DisplayChatNotification(&chatNotification) + getSeenNotificationsCache().Add(dupKey, struct{}{}) if len(pushID) > 0 { mp.AckNotificationSuccess(ctx, []string{pushID}) }