wix / react-native-notifications

React Native Notifications
MIT License
3.23k stars 763 forks source link

Foreground notification event is fired twice on iOS 18 #1051

Open jcloquell opened 5 days ago

jcloquell commented 5 days ago

We noticed that since the recent iOS 18 update, the foreground notification event is being fired twice.

Notifications.events().registerNotificationReceivedForeground callback gets called one time initially, when receiving the actual push notification, and then it's called again after exactly 30 seconds have passed.

This only happens on iOS devices running iOS 18, it seems to work fine on Android and older iOS versions.

EnricoMazzu commented 13 hours ago

This is an ios 18 bug.

https://forums.developer.apple.com/forums/thread/762126

My suggestion (and also the feedback from Apple) : use the identifier fields to implement an app filter and skip the duplicated events.

import { Notification } from "react-native-notifications"
import { Platform } from "react-native"

export interface PushNotificationRegistry {
     shouldFilterNotification(notification: Notification): Boolean
     clear(): void
}

class IdBasedFilterPushRegistry implements PushNotificationRegistry {
    private ids: Array<string>
    private currentIndex = 0
    private capacity: number

    constructor(capacity: number = 10) {
        this.capacity = capacity
        this.ids = []
    }

    shouldFilterNotification(notification: Notification): Boolean {
        if(!notification || !notification.identifier){
            return false
        }
        const alreadyConsumed = !this.isIdentifierAlreadyPresent(notification.identifier) 
        this.storeIdAsConsumed(notification.identifier) 
        return alreadyConsumed
    }

    clear(): void {
        this.currentIndex = 0
        this.ids = []
    }

    private storeIdAsConsumed(identifier: string) {
        // use a circular array approach to prevent memory increase
        this.ids[this.currentIndex] = identifier
        this.currentIndex = (this.currentIndex + 1) % this.capacity
    }

    private isIdentifierAlreadyPresent(id: string) {
        return this.ids.find(item => item == id)
    }
}

class NoopIdFilter implements PushNotificationRegistry {

    shouldFilterNotification(notification: Notification): Boolean {
        return false; 

    }
    clear(): void {

    } 
}

export function createDefaultNotificationRegistry(enableIdFilter: Boolean = false): PushNotificationRegistry {
    return enableIdFilter ? new IdBasedFilterPushRegistry() : new NoopIdFilter()
}

Usage (on foreground events:

const shouldFilter:Boolean =  Platform.OS === 'ios' && parseInt(Platform.Version as string, 10) >= 18;
const registry = createDefaultNotificationRegistry(shouldFilter)

...
const notificationReceivedForegroundCallback = Notifications.events().registerNotificationReceivedForeground((notification: Notification, completion: (response: NotificationCompletion) => void) => { 

          if(registry.shouldFilterNotification(notification)){
                console.log(">>> Blocked by app filter logic")
               ....
                return;
         }
         processNotification(....)
}