[fcm] Add iOS completion handlers
This commit is contained in:
parent
cdb613bdee
commit
4ff20007f5
@ -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
|
||||
|
||||
|
122
lib/modules/messaging/Message.js
Normal file
122
lib/modules/messaging/Message.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
60
lib/modules/messaging/types.js
Normal file
60
lib/modules/messaging/types.js
Normal 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,
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user