diff --git a/docs-react-native/react-native/docs/ios/remote-notification-support.md b/docs-react-native/react-native/docs/ios/remote-notification-support.md index 7954fa492..5fbe37b7a 100644 --- a/docs-react-native/react-native/docs/ios/remote-notification-support.md +++ b/docs-react-native/react-native/docs/ios/remote-notification-support.md @@ -5,7 +5,37 @@ next: / previous: /react-native/docs/ios/permissions --- -It's possible to display a notification with Notifee features from outside the app using remote notifications (a.k.a push notifications). +It's possible to display a notification with Notifee features from outside the app using remote notifications (a.k.a push notifications) in two ways: + - using APNs keys + - using `notifee_options` with our notification service extension helper + +It is recommended to only use a notification service extension if you require an image or need to modify the contents of the notification before its displayed. + +### Using APNs keys + +Notification messages sent through APNs follow the APNs payload format which allows us to be able to specify a category or a custom sound with no extra configuration on the client: + +```json +// FCM +{ + notification: { + title: 'A notification title!', + body: 'A notification body', + }, + apns: { + payload: { + aps: { + category: 'post', // A category that's already been created by your app + sound: 'media/kick.wav', // A local sound file you have inside your app's bundle + ... // any other properties + }, + }, + }, + ... +}; +``` + +### Using `notifee_options` By adding a custom key `notifee_options` in the message payload, the notification will be modified by Notifee before it is finally displayed to the end user. @@ -138,3 +168,33 @@ In your NotifeeNotificationService.m file you should have method `didReceiveNoti Please note, the `id` of the notification is the `request.identifier` and cannot be changed. For this reason, the `id` property in `notifee_options` should be excluded. > if both `attachments` and `image` are present, `attachments` will take precedence over `image` + +### Handling Events + +Currently, notifee supports the following events for remote notifications: +- `PRESSED` +- `ACTION_PRESSED` +- `DISMISSED` + +To know identify when an interaction is from a remote notification, we can check if `notification.remote` is populated: + +```jsx +import { useEffect } from 'react'; +import notifee, { EventType } from '@notifee/react-native'; +function App() { + // Subscribe to events + useEffect(() => { + return notifee.onForegroundEvent(({ type, detail }) => { + console.log('Remote notification info: ', detail.notification?.remote) + switch (type) { + case EventType.DISMISSED: + console.log('User dismissed notification', detail.notification); + break; + case EventType.PRESS: + console.log('User pressed notification', detail.notification); + break; + } + }); + }, []); +} +``` \ No newline at end of file diff --git a/docs-react-native/react-native/docs/release-notes.md b/docs-react-native/react-native/docs/release-notes.md index e34aba420..3f9cf5eac 100644 --- a/docs-react-native/react-native/docs/release-notes.md +++ b/docs-react-native/react-native/docs/release-notes.md @@ -5,6 +5,15 @@ next: /react-native/docs/usage previous: /react-native/docs/installation --- +## next +- **[iOS]: BREAKING CHANGE** : Notifee now handles response events for remote notifications on iOS: + - PRESSED + - ACTION_PRESSED + - DISMISSED +This allows quick actions from remote notifications to be supported without the need of a NSE [[Learn More]](https://notifee.app/react-native/docs/ios/remote-notification-support) + + **`onNotificationOpenedApp` and `getInitialNotification` from `RNFB Messaging` will no longer trigger as notifee will handle the event. Should not require any code changes to these `RNFB` event handlers, as events on Android will continue to work as normal** + ## 6.0.0 - **[Android] BREAKING CHANGE**: Added support for requesting permission on Android 13 via `requestPermission`, the minimum compileSdkVersion required has increased to 33. And, to support this feature, the targetSdkVersion must also be increased to 33. ## 5.7.0 diff --git a/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m b/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m index 41a13d0cf..bf5dbb603 100644 --- a/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m +++ b/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m @@ -158,7 +158,11 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center NSDictionary *notifeeNotification = response.notification.request.content.userInfo[kNotifeeUserInfoNotification]; - // we only care about notifications created through notifee + // handle notification outside of notifee + if (notifeeNotification == nil) { + notifeeNotification = [NotifeeCoreUtil parseUNNotificationRequest:response.notification.request]; + } + if (notifeeNotification != nil) { if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) { // post DISMISSED event, only triggers if notification has a categoryId diff --git a/ios/NotifeeCore/NotifeeCoreUtil.m b/ios/NotifeeCore/NotifeeCoreUtil.m index d08ba6189..ffad4fc67 100644 --- a/ios/NotifeeCore/NotifeeCoreUtil.m +++ b/ios/NotifeeCore/NotifeeCoreUtil.m @@ -576,13 +576,54 @@ + (NSNumber *)convertToTimestamp:(NSDate *)date { * * @param request UNNotificationRequest */ -+ (NSDictionary *)parseUNNotificationRequest:(UNNotificationRequest *)request { ++ (NSMutableDictionary *)parseUNNotificationRequest:(UNNotificationRequest *)request { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - NSMutableDictionary *iosDict = [NSMutableDictionary dictionary]; + dictionary = [self parseUNNotificationContent:request.content]; dictionary[@"id"] = request.identifier; - UNNotificationContent *content = request.content; + NSDictionary *userInfo = request.content.userInfo; + + // Check for remote details + if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) { + NSMutableDictionary *remote = [NSMutableDictionary dictionary]; + + remote[@"messageId"] = userInfo[@"gcm.message_id"]; + remote[@"senderId"] = userInfo[@"google.c.sender.id"]; + + if (userInfo[@"aps"] != nil) { + remote[@"mutableContent"] = userInfo[@"aps"][@"mutable-content"]; + remote[@"contentAvailable"] = userInfo[@"aps"][@"content-available"]; + } + + dictionary[@"remote"] = remote; + } + + dictionary[@"data"] = [self parseDataFromUserInfo:userInfo]; + + return dictionary; +} + ++ (NSMutableDictionary *)parseDataFromUserInfo:(NSDictionary *)userInfo { + NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; + for (id key in userInfo) { + // build data dict from remaining keys but skip keys that shouldn't be included in data + if ([key isEqualToString:@"aps"] || [key hasPrefix:@"gcm."] || [key hasPrefix:@"google."] || + // notifee or notifee_options + [key hasPrefix:@"notifee"] || + // fcm_options + [key hasPrefix:@"fcm"]) { + continue; + } + data[key] = userInfo[key]; + } + + return data; +} + ++ (NSMutableDictionary *)parseUNNotificationContent:(UNNotificationContent *)content { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + NSMutableDictionary *iosDict = [NSMutableDictionary dictionary]; dictionary[@"subtitle"] = content.subtitle; dictionary[@"body"] = content.body; @@ -627,19 +668,39 @@ + (NSDictionary *)parseUNNotificationRequest:(UNNotificationRequest *)request { } } - // TODO: parse sound - // if (content.sound != nil) { - // iosDict[@"sound"] = content.sound; - // } + if (content.attachments != nil) { + // TODO: parse attachments + } - // TODO: parse attachments - // if (content.attachments != nil) { - // iosDict[@"attachments"] = - // [NotifeeCoreUtil DictionaryArrayToNotificationAttachments:content.attachments]; - // } + // sound + if (content.sound != nil) { + if ([content.sound isKindOfClass:[NSString class]]) { + iosDict[@"sound"] = content.sound; + } else if ([content.sound isKindOfClass:[NSDictionary class]]) { + NSDictionary *soundDict = content.sound; + NSMutableDictionary *notificationIOSSound = [[NSMutableDictionary alloc] init]; + + // ios.sound.name String + if (soundDict[@"name"] != nil) { + notificationIOSSound[@"name"] = soundDict[@"name"]; + } - dictionary[@"ios"] = iosDict; + // sound.critical Boolean + if (soundDict[@"critical"] != nil) { + notificationIOSSound[@"critical"] = soundDict[@"critical"]; + } + // ios.sound.volume Number + if (soundDict[@"volume"] != nil) { + notificationIOSSound[@"volume"] = soundDict[@"volume"]; + } + + // ios.sound + iosDict[@"sound"] = notificationIOSSound; + } + } + + dictionary[@"ios"] = iosDict; return dictionary; }