Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ios): handle response events for remote notifications [next] #236

Merged
merged 3 commits into from
Sep 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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;
}
});
}, []);
}
```
9 changes: 9 additions & 0 deletions docs-react-native/react-native/docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI it looks like there is a conflict with the Braze SDK with this line
To reproduce : install notifee and braze. Open your app, send a Braze test IAM campaign, click on the notification to show the IAM. No IAM is shown.
And possibly conflicts with other SDKs

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workaround is patching parseUNNotificationRequest with

+  if (userInfo != nil && userInfo[keyToExclude] != nil) {
+      return nil;
+  }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no! Looks like an easy enough local patch-package fix for you (I'd bet $1 you already have that running ;-) ) but a PR here is something we could integrate

}

if (notifeeNotification != nil) {
if ([response.actionIdentifier isEqualToString:UNNotificationDismissActionIdentifier]) {
// post DISMISSED event, only triggers if notification has a categoryId
Expand Down
87 changes: 74 additions & 13 deletions ios/NotifeeCore/NotifeeCoreUtil.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down