2
0
mirror of synced 2025-01-10 22:26:02 +00:00

[fcm] Add iOS completion handlers

This commit is contained in:
Chris Bianca 2018-02-08 17:07:20 +00:00
parent cdb613bdee
commit 4ff20007f5
4 changed files with 275 additions and 43 deletions

View File

@ -17,6 +17,7 @@
@import UserNotifications;
@interface RNFirebaseMessaging () <UNUserNotificationCenterDelegate>
@property (nonatomic, strong) NSMutableDictionary *callbackHandlers;
@end
#endif
@ -53,6 +54,9 @@ RCT_EXPORT_MODULE()
// Set static instance for use from AppDelegate
theRNFirebaseMessaging = self;
// Initialise callback handlers dictionary
_callbackHandlers = [NSMutableDictionary dictionary];
}
// *******************************************************
@ -63,7 +67,7 @@ RCT_EXPORT_MODULE()
// Listen for background messages
- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo {
BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive);
NSDictionary *message = [self parseUserInfo:userInfo clickAction:nil openedFromTray:isFromBackground];
NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" clickAction:nil openedFromTray:isFromBackground];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
@ -72,11 +76,11 @@ RCT_EXPORT_MODULE()
- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive);
NSDictionary *message = [self parseUserInfo:userInfo clickAction:nil openedFromTray:isFromBackground];
NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" clickAction:nil openedFromTray:isFromBackground];
[_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
// TODO: FetchCompletionHandler?
}
// Listen for permission response
@ -107,7 +111,9 @@ RCT_EXPORT_MODULE()
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
NSDictionary *message = [self parseUNNotification:notification openedFromTray:false];
NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification" openedFromTray:false];
[_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
@ -123,12 +129,11 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
#else
withCompletionHandler:(void(^)())completionHandler {
#endif
NSDictionary *userInfo = [self parseUNNotification:response.notification openedFromTray:true];
NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse" openedFromTray:true];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:userInfo];
[_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]];
// TODO: Validate this
completionHandler();
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
#endif
@ -210,7 +215,7 @@ RCT_EXPORT_METHOD(getBadge: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromise
RCT_EXPORT_METHOD(getInitialMessage:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){
NSDictionary *notification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (notification) {
NSDictionary *message = [self parseUserInfo:notification clickAction:nil openedFromTray:true];
NSDictionary *message = [self parseUserInfo:notification messageType:@"InitialMessage" clickAction:nil openedFromTray:true];
resolve(message);
} else {
resolve(nil);
@ -260,6 +265,60 @@ RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic) {
[[FIRMessaging messaging] unsubscribeFromTopic:topic];
}
// Response handler methods
RCT_EXPORT_METHOD(finishNotificationResponse: (NSString*) messageId) {
void(^callbackHandler)() = [_callbackHandlers objectForKey:messageId];
if (!callbackHandler) {
NSLog(@"There is no callback handler for messageId: %@", messageId);
return;
}
callbackHandler();
[_callbackHandlers removeObjectForKey:messageId];
}
RCT_EXPORT_METHOD(finishPresentNotification: (NSString*) messageId
result: (NSString*) result) {
void(^callbackHandler)(UNNotificationPresentationOptions) = [_callbackHandlers objectForKey:messageId];
if (!callbackHandler) {
NSLog(@"There is no callback handler for messageId: %@", messageId);
return;
}
UNNotificationPresentationOptions options;
if ([result isEqualToString:@"UNNotificationPresentationOptionAll"]) {
options = UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound;
} else if ([result isEqualToString:@"UNNotificationPresentationOptionNone"]) {
options = UNNotificationPresentationOptionNone;
} else {
NSLog(@"Invalid result for PresentNotification: %@", result);
return;
}
callbackHandler(options);
[_callbackHandlers removeObjectForKey:messageId];
}
RCT_EXPORT_METHOD(finishRemoteNotification: (NSString*) messageId
result: (NSString*) result) {
void(^callbackHandler)(UIBackgroundFetchResult) = [_callbackHandlers objectForKey:messageId];
if (!callbackHandler) {
NSLog(@"There is no callback handler for messageId: %@", messageId);
return;
}
UIBackgroundFetchResult fetchResult;
if ([result isEqualToString:@"UIBackgroundFetchResultNewData"]) {
fetchResult = UIBackgroundFetchResultNewData;
} else if ([result isEqualToString:@"UIBackgroundFetchResultNoData"]) {
fetchResult = UIBackgroundFetchResultNoData;
} else if ([result isEqualToString:@"UIBackgroundFetchResultFailed"]) {
fetchResult = UIBackgroundFetchResultFailed;
} else {
NSLog(@"Invalid result for PresentNotification: %@", result);
return;
}
callbackHandler(fetchResult);
[_callbackHandlers removeObjectForKey:messageId];
}
// ** Start internals **
- (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage
@ -307,6 +366,7 @@ RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic) {
data[k1] = appData[k1];
}
}
message[@"messageType"] = @"RemoteMessage";
message[@"data"] = data;
message[@"openedFromTray"] = @(false);
@ -314,14 +374,16 @@ RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic) {
}
- (NSDictionary*)parseUNNotification:(UNNotification *)notification
messageType:(NSString *)messageType
openedFromTray:(bool)openedFromTray {
NSDictionary *userInfo = notification.request.content.userInfo;
NSString *clickAction = notification.request.content.categoryIdentifier;
return [self parseUserInfo:userInfo clickAction:clickAction openedFromTray:openedFromTray];
return [self parseUserInfo:userInfo messageType:messageType clickAction:clickAction openedFromTray:openedFromTray];
}
- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo
messageType:(NSString *) messageType
clickAction:(NSString *) clickAction
openedFromTray:(bool)openedFromTray {
NSMutableDictionary *message = [[NSMutableDictionary alloc] init];
@ -383,6 +445,12 @@ RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic) {
if (!notif[@"clickAction"] && clickAction) {
notif[@"clickAction"] = clickAction;
}
// Generate a message ID if one was not present in the notification
// This is used for resolving click handlers
if (!message[@"messageId"]) {
message[@"messageId"] = [[NSUUID UUID] UUIDString];
}
message[@"data"] = data;
message[@"notification"] = notif;
@ -406,3 +474,4 @@ RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic) {
@implementation RNFirebaseMessaging
@end
#endif

View File

@ -0,0 +1,122 @@
/**
* @flow
* Message representation wrapper
*/
import { Platform } from 'react-native';
import { getNativeModule } from '../../utils/native';
import {
MessageType,
RemoteNotificationResult,
WillPresentNotificationResult,
} from './types';
import type Messaging from './';
import type {
MessageTypeType,
NativeMessage,
Notification,
RemoteNotificationResultType,
WillPresentNotificationResultType,
} from './types';
/**
* @class Message
*/
export default class Message {
_finished: boolean;
_messaging: Messaging;
_message: NativeMessage;
constructor(messaging: Messaging, message: NativeMessage) {
this._messaging = messaging;
this._message = message;
}
get collapseKey(): ?string {
return this._message.collapseKey;
}
get data(): { [string]: string } {
return this._message.data;
}
get from(): ?string {
return this._message.from;
}
get messageId(): ?string {
return this._message.messageId;
}
get messageType(): ?MessageTypeType {
return this._message.messageType;
}
get openedFromTray(): boolean {
return this._message.openedFromTray;
}
get notification(): ?Notification {
return this._message.notification;
}
get sentTime(): ?number {
return this._message.sentTime;
}
get to(): ?string {
return this._message.to;
}
get ttl(): ?number {
return this._message.ttl;
}
finish(
result?: RemoteNotificationResultType | WillPresentNotificationResultType
): void {
if (Platform.OS !== 'ios') {
return;
}
if (!this._finished) {
this._finished = true;
switch (this.messageType) {
case MessageType.NotificationResponse:
getNativeModule(this._messaging).finishNotificationResponse(
this.messageId
);
break;
case MessageType.PresentNotification:
if (
result &&
!Object.values(WillPresentNotificationResult).includes(result)
) {
throw new Error(`Invalid WillPresentNotificationResult: ${result}`);
}
getNativeModule(this._messaging).finishPresentNotification(
this.messageId,
result || WillPresentNotificationResult.None
);
break;
case MessageType.RemoteNotificationHandler:
if (
result &&
!Object.values(RemoteNotificationResult).includes(result)
) {
throw new Error(`Invalid RemoteNotificationResult: ${result}`);
}
getNativeModule(this._messaging).finishRemoteNotification(
this.messageId,
result || RemoteNotificationResult.NoData
);
break;
default:
break;
}
}
}
}

View File

@ -8,37 +8,10 @@ import { getLogger } from '../../utils/log';
import ModuleBase from '../../utils/ModuleBase';
import { getNativeModule } from '../../utils/native';
import { isFunction, isObject } from '../../utils';
import Message from './Message';
import type App from '../core/firebase-app';
type Notification = {
body: string,
bodyLocalizationArgs?: string[],
bodyLocalizationKey?: string,
clickAction?: string,
color?: string,
icon?: string,
link?: string,
sound: string,
subtitle?: string,
tag?: string,
title: string,
titleLocalizationArgs?: string[],
titleLocalizationKey?: string,
};
type Message = {
collapseKey?: string,
data: { [string]: string },
from?: string,
messageId: string,
messageType?: string,
openedFromTray: boolean,
notification?: Notification,
sentTime?: number,
to?: string,
ttl?: number,
};
import type { NativeMessage } from './types';
type OnMessage = Message => any;
@ -105,8 +78,9 @@ export default class Messaging extends ModuleBase {
}
onMessage(nextOrObserver: OnMessage | OnMessageObserver): () => any {
let listener;
let listener: Message => any;
if (isFunction(nextOrObserver)) {
// $FlowBug: Not coping with the overloaded method signature
listener = nextOrObserver;
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
listener = nextOrObserver.next;
@ -118,11 +92,18 @@ export default class Messaging extends ModuleBase {
// TODO: iOS finish
getLogger(this).info('Creating onMessage listener');
SharedEventEmitter.addListener('onMessage', listener);
const wrappedListener = async (nativeMessage: NativeMessage) => {
const message = new Message(this, nativeMessage);
await listener(message);
message.finish();
};
SharedEventEmitter.addListener('onMessage', wrappedListener);
return () => {
getLogger(this).info('Removing onMessage listener');
SharedEventEmitter.removeListener('onMessage', listener);
SharedEventEmitter.removeListener('onMessage', wrappedListener);
};
}

View File

@ -0,0 +1,60 @@
/**
* @flow
*/
export const MessageType = {
InitialMessage: 'InitialMessage',
NotificationResponse: 'NotificationResponse',
PresentNotification: 'PresentNotification',
RemoteMessage: 'RemoteMessage',
RemoteNotification: 'RemoteNotification',
RemoteNotificationHandler: 'RemoteNotificationHandler',
};
export const RemoteNotificationResult = {
NewData: 'UIBackgroundFetchResultNewData',
NoData: 'UIBackgroundFetchResultNoData',
ResultFailed: 'UIBackgroundFetchResultFailed',
};
export const WillPresentNotificationResult = {
All: 'UNNotificationPresentationOptionAll',
None: 'UNNotificationPresentationOptionNone',
};
export type MessageTypeType = $Values<typeof MessageType>;
export type RemoteNotificationResultType = $Values<
typeof RemoteNotificationResult
>;
export type WillPresentNotificationResultType = $Values<
typeof WillPresentNotificationResult
>;
export type Notification = {
body: string,
bodyLocalizationArgs?: string[],
bodyLocalizationKey?: string,
clickAction?: string,
color?: string,
icon?: string,
link?: string,
sound: string,
subtitle?: string,
tag?: string,
title: string,
titleLocalizationArgs?: string[],
titleLocalizationKey?: string,
};
export type NativeMessage = {
collapseKey?: string,
data: { [string]: string },
from?: string,
messageId: string,
messageType?: MessageTypeType,
openedFromTray: boolean,
notification?: Notification,
sentTime?: number,
to?: string,
ttl?: number,
};