[notifications] Refactor for better support of separate messages

This commit is contained in:
Chris Bianca 2018-02-22 15:52:24 +00:00
parent 831eec82f7
commit 303cb4c428
12 changed files with 463 additions and 326 deletions

View File

@ -39,8 +39,8 @@ static NSString *const MESSAGING_TOKEN_REFRESHED = @"messaging_token_refreshed";
static NSString *const MESSAGING_NOTIFICATION_RECEIVED = @"messaging_notification_received"; static NSString *const MESSAGING_NOTIFICATION_RECEIVED = @"messaging_notification_received";
// Notifications // Notifications
static NSString *const NOTIFICATIONS_NOTIFICATION_CLICKED = @"notifications_notification_clicked";
static NSString *const NOTIFICATIONS_NOTIFICATION_DISPLAYED = @"notifications_notification_displayed"; static NSString *const NOTIFICATIONS_NOTIFICATION_DISPLAYED = @"notifications_notification_displayed";
static NSString *const NOTIFICATIONS_NOTIFICATION_PRESSED = @"notifications_notification_pressed";
static NSString *const NOTIFICATIONS_NOTIFICATION_RECEIVED = @"notifications_notification_received"; static NSString *const NOTIFICATIONS_NOTIFICATION_RECEIVED = @"notifications_notification_received";
// AdMob // AdMob

View File

@ -15,7 +15,8 @@
@property _Nullable RCTPromiseResolveBlock permissionResolver; @property _Nullable RCTPromiseResolveBlock permissionResolver;
#if !TARGET_OS_TV #if !TARGET_OS_TV
- (void)didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings; - (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo;
- (void)didRegisterUserNotificationSettings:(nonnull UIUserNotificationSettings *)notificationSettings;
#endif #endif
@end @end

View File

@ -70,6 +70,12 @@ RCT_EXPORT_MODULE()
_permissionResolver = nil; _permissionResolver = nil;
} }
// Listen for FCM data messages that arrive as a remote notification
- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo {
NSDictionary *message = [self parseUserInfo:userInfo];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
// ******************************************************* // *******************************************************
// ** Finish AppDelegate methods // ** Finish AppDelegate methods
// ******************************************************* // *******************************************************
@ -88,8 +94,15 @@ RCT_EXPORT_MODULE()
// Listen for data messages in the foreground // Listen for data messages in the foreground
- (void)applicationReceivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage { - (void)applicationReceivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage openedFromTray:false]; NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
}
// Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground.
// To enable direct data messages, you can set [Messaging messaging].shouldEstablishDirectChannel to YES.
- (void)messaging:(nonnull FIRMessaging *)messaging
didReceiveMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage {
NSDictionary *message = [self parseFIRMessagingRemoteMessage:remoteMessage];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message];
} }
@ -250,8 +263,7 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId
// ** Start internals ** // ** Start internals **
- (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage - (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
openedFromTray:(bool)openedFromTray {
NSDictionary *appData = remoteMessage.appData; NSDictionary *appData = remoteMessage.appData;
NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; NSMutableDictionary *message = [[NSMutableDictionary alloc] init];
@ -262,46 +274,46 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId
} else if ([k1 isEqualToString:@"from"]) { } else if ([k1 isEqualToString:@"from"]) {
message[@"from"] = appData[k1]; message[@"from"] = appData[k1];
} else if ([k1 isEqualToString:@"notification"]) { } else if ([k1 isEqualToString:@"notification"]) {
NSDictionary *notification = appData[k1]; // Ignore for messages
NSMutableDictionary *notif = [[NSMutableDictionary alloc] init];
for (id k2 in notification) {
if ([k2 isEqualToString:@"badge"]) {
notif[@"badge"] = notification[k2];
} else if ([k2 isEqualToString:@"body"]) {
notif[@"body"] = notification[k2];
} else if ([k2 isEqualToString:@"body_loc_args"]) {
notif[@"bodyLocalizationArgs"] = notification[k2];
} else if ([k2 isEqualToString:@"body_loc_key"]) {
notif[@"bodyLocalizationKey"] = notification[k2];
} else if ([k2 isEqualToString:@"click_action"]) {
notif[@"clickAction"] = notification[k2];
} else if ([k2 isEqualToString:@"sound"]) {
notif[@"sound"] = notification[k2];
} else if ([k2 isEqualToString:@"subtitle"]) {
notif[@"subtitle"] = notification[k2];
} else if ([k2 isEqualToString:@"title"]) {
notif[@"title"] = notification[k2];
} else if ([k2 isEqualToString:@"title_loc_args"]) {
notif[@"titleLocalizationArgs"] = notification[k2];
} else if ([k2 isEqualToString:@"title_loc_key"]) {
notif[@"titleLocalizationKey"] = notification[k2];
} else {
NSLog(@"Unknown notification key: %@", k2);
}
}
message[@"notification"] = notif;
} else { } else {
// Assume custom data key // Assume custom data key
data[k1] = appData[k1]; data[k1] = appData[k1];
} }
} }
message[@"messageType"] = @"RemoteMessage";
message[@"data"] = data; message[@"data"] = data;
message[@"openedFromTray"] = @(false);
return message; return message;
} }
- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo {
NSMutableDictionary *message = [[NSMutableDictionary alloc] init];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
for (id k1 in userInfo) {
if ([k1 isEqualToString:@"aps"]) {
// Ignore notification section
} else if ([k1 isEqualToString:@"gcm.message_id"]) {
message[@"messageId"] = userInfo[k1];
} else if ([k1 isEqualToString:@"google.c.a.ts"]) {
message[@"sentTime"] = userInfo[k1];
} else if ([k1 isEqualToString:@"gcm.n.e"]
|| [k1 isEqualToString:@"gcm.notification.sound2"]
|| [k1 isEqualToString:@"google.c.a.c_id"]
|| [k1 isEqualToString:@"google.c.a.c_l"]
|| [k1 isEqualToString:@"google.c.a.e"]
|| [k1 isEqualToString:@"google.c.a.udt"]) {
// Ignore known keys
} else {
// Assume custom data
data[k1] = userInfo[k1];
}
}
message[@"data"] = data;
return message;
}
- (NSArray<NSString *> *)supportedEvents { - (NSArray<NSString *> *)supportedEvents {
return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED]; return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED];
} }
@ -317,4 +329,3 @@ RCT_EXPORT_METHOD(completeRemoteNotification: (NSString*) messageId
@implementation RNFirebaseMessaging @implementation RNFirebaseMessaging
@end @end
#endif #endif

View File

@ -2,6 +2,7 @@
#if __has_include(<FirebaseMessaging/FIRMessaging.h>) #if __has_include(<FirebaseMessaging/FIRMessaging.h>)
#import "RNFirebaseEvents.h" #import "RNFirebaseEvents.h"
#import "RNFirebaseMessaging.h"
#import "RNFirebaseUtil.h" #import "RNFirebaseUtil.h"
#import <React/RCTUtils.h> #import <React/RCTUtils.h>
@ -37,7 +38,7 @@ RCT_EXPORT_MODULE();
- (void)configure { - (void)configure {
// If we're on iOS 10 then we need to set this as a delegate for the UNUserNotificationCenter // If we're on iOS 10 then we need to set this as a delegate for the UNUserNotificationCenter
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
[UNUserNotificationCenter currentNotificationCenter].delegate = self; // [UNUserNotificationCenter currentNotificationCenter].delegate = self;
#endif #endif
// Set static instance for use from AppDelegate // Set static instance for use from AppDelegate
@ -50,26 +51,78 @@ RCT_EXPORT_MODULE();
// ******************************************************* // *******************************************************
- (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification { - (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification {
NSString *event;
if (RCTSharedApplication().applicationState == UIApplicationStateBackground) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
} else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_PRESSED;
} else {
// notification_received
event = NOTIFICATIONS_NOTIFICATION_RECEIVED;
}
NSDictionary *message = [self parseUILocalNotification:notification];
[RNFirebaseUtil sendJSEvent:self name:event body:message];
} }
// Listen for background messages // Listen for background messages
- (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo { - (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo {
BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive); // FCM Data messages come through here if they specify content-available=true
NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" clickAction:nil openedFromTray:isFromBackground]; // Pass them over to the RNFirebaseMessaging handler instead
if (userInfo[@"aps"] && ((NSDictionary*)userInfo[@"aps"]).count == 1 && userInfo[@"aps"][@"content-available"]) {
[[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo];
return;
}
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; NSString *event;
if (RCTSharedApplication().applicationState == UIApplicationStateBackground) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
} else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_PRESSED;
} else {
// notification_received
event = NOTIFICATIONS_NOTIFICATION_RECEIVED;
}
// TODO: Proper notification structure
NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotification" clickAction:nil];
[RNFirebaseUtil sendJSEvent:self name:event body:message];
} }
// Listen for background messages // Listen for background messages
- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo - (void)didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
BOOL isFromBackground = (RCTSharedApplication().applicationState == UIApplicationStateInactive); // FCM Data messages come through here if they specify content-available=true
NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" clickAction:nil openedFromTray:isFromBackground]; // Pass them over to the RNFirebaseMessaging handler instead
if (userInfo[@"aps"] && ((NSDictionary*)userInfo[@"aps"]).count == 1 && userInfo[@"aps"][@"content-available"]) {
[[RNFirebaseMessaging instance] didReceiveRemoteNotification:userInfo];
return;
}
NSString *event;
if (RCTSharedApplication().applicationState == UIApplicationStateBackground) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
} else if (RCTSharedApplication().applicationState == UIApplicationStateInactive) {
// notification_displayed
event = NOTIFICATIONS_NOTIFICATION_PRESSED;
} else {
// notification_received
event = NOTIFICATIONS_NOTIFICATION_RECEIVED;
}
// TODO: Proper notification structure
NSDictionary *message = [self parseUserInfo:userInfo messageType:@"RemoteNotificationHandler" clickAction:nil];
// TODO: Callback handler
// [_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]]; // [_callbackHandlers setObject:[completionHandler copy] forKey:message[@"messageId"]];
[RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; [RNFirebaseUtil sendJSEvent:self name:event body:message];
} }
// ******************************************************* // *******************************************************
@ -93,7 +146,7 @@ RCT_EXPORT_MODULE();
NSString *event; NSString *event;
UNNotificationPresentationOptions options; UNNotificationPresentationOptions options;
NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification" openedFromTray:false]; NSDictionary *message = [self parseUNNotification:notification messageType:@"PresentNotification"];
if (isFcm || isScheduled) { if (isFcm || isScheduled) {
// If app is in the background // If app is in the background
@ -116,9 +169,7 @@ RCT_EXPORT_MODULE();
event = NOTIFICATIONS_NOTIFICATION_DISPLAYED; event = NOTIFICATIONS_NOTIFICATION_DISPLAYED;
} }
if (event) { [RNFirebaseUtil sendJSEvent:self name:event body:message];
[RNFirebaseUtil sendJSEvent:self name:event body:message];
}
completionHandler(options); completionHandler(options);
} }
@ -130,9 +181,9 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
#else #else
withCompletionHandler:(void(^)())completionHandler { withCompletionHandler:(void(^)())completionHandler {
#endif #endif
NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse" openedFromTray:true]; NSDictionary *message = [self parseUNNotification:response.notification messageType:@"NotificationResponse"];
[RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_CLICKED body:message]; [RNFirebaseUtil sendJSEvent:self name:NOTIFICATIONS_NOTIFICATION_PRESSED body:message];
completionHandler(); completionHandler();
} }
@ -200,7 +251,13 @@ RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecte
NSDictionary *notification = [self parseUILocalNotification:localNotification]; NSDictionary *notification = [self parseUILocalNotification:localNotification];
resolve(notification); resolve(notification);
} else { } else {
resolve(nil); NSDictionary *remoteNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
if (remoteNotification) {
NSDictionary *message = [self parseUserInfo:remoteNotification messageType:@"InitialMessage" clickAction:nil];
resolve(message);
} else {
resolve(nil);
}
} }
} }
@ -485,7 +542,7 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
} }
ios[@"attachments"] = attachments; ios[@"attachments"] = attachments;
} }
if (localNotification.content.badge) { if (localNotification.content.badge) {
ios[@"badge"] = localNotification.content.badge; ios[@"badge"] = localNotification.content.badge;
} }
@ -504,21 +561,20 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
} }
- (NSDictionary*)parseUNNotification:(UNNotification *)notification - (NSDictionary*)parseUNNotification:(UNNotification *)notification
messageType:(NSString *)messageType messageType:(NSString *)messageType {
openedFromTray:(bool)openedFromTray {
NSDictionary *userInfo = notification.request.content.userInfo; NSDictionary *userInfo = notification.request.content.userInfo;
NSString *clickAction = notification.request.content.categoryIdentifier; NSString *clickAction = notification.request.content.categoryIdentifier;
return [self parseUserInfo:userInfo messageType:messageType clickAction:clickAction openedFromTray:openedFromTray]; return [self parseUserInfo:userInfo messageType:messageType clickAction:clickAction];
} }
- (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo - (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo
messageType:(NSString *) messageType messageType:(NSString *) messageType
clickAction:(NSString *) clickAction clickAction:(NSString *) clickAction {
openedFromTray:(bool)openedFromTray {
NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; NSMutableDictionary *notification = [[NSMutableDictionary alloc] init];
NSMutableDictionary *notif = [[NSMutableDictionary alloc] init];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
NSMutableDictionary *ios = [[NSMutableDictionary alloc] init];
for (id k1 in userInfo) { for (id k1 in userInfo) {
if ([k1 isEqualToString:@"aps"]) { if ([k1 isEqualToString:@"aps"]) {
@ -528,37 +584,42 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
NSDictionary *alert = aps[k2]; NSDictionary *alert = aps[k2];
for (id k3 in alert) { for (id k3 in alert) {
if ([k3 isEqualToString:@"body"]) { if ([k3 isEqualToString:@"body"]) {
notif[@"body"] = alert[k3]; notification[@"body"] = alert[k3];
} else if ([k3 isEqualToString:@"loc-args"]) { } else if ([k3 isEqualToString:@"loc-args"]) {
notif[@"bodyLocalizationArgs"] = alert[k3]; // TODO: What to do with this?
// notif[@"bodyLocalizationArgs"] = alert[k3];
} else if ([k3 isEqualToString:@"loc-key"]) { } else if ([k3 isEqualToString:@"loc-key"]) {
notif[@"bodyLocalizationKey"] = alert[k3]; // TODO: What to do with this?
// notif[@"bodyLocalizationKey"] = alert[k3];
} else if ([k3 isEqualToString:@"subtitle"]) { } else if ([k3 isEqualToString:@"subtitle"]) {
notif[@"subtitle"] = alert[k3]; notification[@"subtitle"] = alert[k3];
} else if ([k3 isEqualToString:@"title"]) { } else if ([k3 isEqualToString:@"title"]) {
notif[@"title"] = alert[k3]; notification[@"title"] = alert[k3];
} else if ([k3 isEqualToString:@"title-loc-args"]) { } else if ([k3 isEqualToString:@"title-loc-args"]) {
notif[@"titleLocalizationArgs"] = alert[k3]; // TODO: What to do with this?
// notif[@"titleLocalizationArgs"] = alert[k3];
} else if ([k3 isEqualToString:@"title-loc-key"]) { } else if ([k3 isEqualToString:@"title-loc-key"]) {
notif[@"titleLocalizationKey"] = alert[k3]; // TODO: What to do with this?
// notif[@"titleLocalizationKey"] = alert[k3];
} else { } else {
NSLog(@"Unknown alert key: %@", k2); NSLog(@"Unknown alert key: %@", k2);
} }
} }
} else if ([k2 isEqualToString:@"badge"]) { } else if ([k2 isEqualToString:@"badge"]) {
notif[@"badge"] = aps[k2]; ios[@"badge"] = aps[k2];
} else if ([k2 isEqualToString:@"category"]) { } else if ([k2 isEqualToString:@"category"]) {
notif[@"clickAction"] = aps[k2]; ios[@"category"] = aps[k2];
} else if ([k2 isEqualToString:@"sound"]) { } else if ([k2 isEqualToString:@"sound"]) {
notif[@"sound"] = aps[k2]; notification[@"sound"] = aps[k2];
} else { } else {
NSLog(@"Unknown aps key: %@", k2); NSLog(@"Unknown aps key: %@", k2);
} }
} }
} else if ([k1 isEqualToString:@"gcm.message_id"]) { } else if ([k1 isEqualToString:@"gcm.message_id"]) {
message[@"messageId"] = userInfo[k1]; notification[@"notificationId"] = userInfo[k1];
} else if ([k1 isEqualToString:@"google.c.a.ts"]) { } else if ([k1 isEqualToString:@"google.c.a.ts"]) {
message[@"sentTime"] = userInfo[k1]; // TODO: What to do with this?
// message[@"sentTime"] = userInfo[k1];
} else if ([k1 isEqualToString:@"gcm.n.e"] } else if ([k1 isEqualToString:@"gcm.n.e"]
|| [k1 isEqualToString:@"gcm.notification.sound2"] || [k1 isEqualToString:@"gcm.notification.sound2"]
|| [k1 isEqualToString:@"google.c.a.c_id"] || [k1 isEqualToString:@"google.c.a.c_id"]
@ -572,26 +633,17 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
} }
} }
if (!notif[@"clickAction"] && clickAction) { // TODO: What to do with this?
notif[@"clickAction"] = clickAction; // message[@"messageType"] = messageType;
}
// Generate a message ID if one was not present in the notification notification[@"data"] = data;
// This is used for resolving click handlers notification[@"ios"] = ios;
if (!message[@"messageId"]) {
message[@"messageId"] = [[NSUUID UUID] UUIDString];
}
message[@"messageType"] = messageType;
message[@"data"] = data; return notification;
message[@"notification"] = notif;
message[@"openedFromTray"] = @(openedFromTray);
return message;
} }
- (NSArray<NSString *> *)supportedEvents { - (NSArray<NSString *> *)supportedEvents {
return @[NOTIFICATIONS_NOTIFICATION_CLICKED, NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_RECEIVED]; return @[NOTIFICATIONS_NOTIFICATION_DISPLAYED, NOTIFICATIONS_NOTIFICATION_PRESSED, NOTIFICATIONS_NOTIFICATION_RECEIVED];
} }
+ (BOOL)requiresMainQueueSetup + (BOOL)requiresMainQueueSetup
@ -605,4 +657,3 @@ RCT_EXPORT_METHOD(scheduleNotification:(NSDictionary*) notification
@implementation RNFirebaseNotifications @implementation RNFirebaseNotifications
@end @end
#endif #endif

View File

@ -11,9 +11,7 @@ import {
} from './types'; } from './types';
import type Messaging from './'; import type Messaging from './';
import type { import type {
MessageTypeType,
NativeMessage, NativeMessage,
Notification,
PresentNotificationResultType, PresentNotificationResultType,
RemoteNotificationResultType, RemoteNotificationResultType,
} from './types'; } from './types';
@ -47,18 +45,6 @@ export default class Message {
return this._message.messageId; 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 { get sentTime(): ?number {
return this._message.sentTime; return this._message.sentTime;
} }

View File

@ -55,8 +55,8 @@ export default class Messaging extends ModuleBase {
// sub to internal native event - this fans out to // sub to internal native event - this fans out to
// public event name: onMessage // public event name: onMessage
'messaging_message_received', 'messaging_message_received',
(message: Message) => { (message: NativeMessage) => {
SharedEventEmitter.emit('onMessage', message); SharedEventEmitter.emit('onMessage', new Message(this, message));
} }
); );
@ -89,8 +89,7 @@ export default class Messaging extends ModuleBase {
getLogger(this).info('Creating onMessage listener'); getLogger(this).info('Creating onMessage listener');
const wrappedListener = async (nativeMessage: NativeMessage) => { const wrappedListener = async (message: Message) => {
const message = new Message(this, nativeMessage);
await listener(message); await listener(message);
message.complete(); message.complete();
}; };

View File

@ -51,9 +51,6 @@ export type NativeMessage = {
data: { [string]: string }, data: { [string]: string },
from?: string, from?: string,
messageId: string, messageId: string,
messageType?: MessageTypeType,
openedFromTray: boolean,
notification?: Notification,
sentTime?: number, sentTime?: number,
to?: string, to?: string,
ttl?: number, ttl?: number,

View File

@ -2,128 +2,29 @@
* @flow * @flow
* AndroidNotification representation wrapper * AndroidNotification representation wrapper
*/ */
import { Category } from './types';
import type Notification from './Notification'; import type Notification from './Notification';
import type {
type Lights = { BadgeIconTypeType,
argb: number, CategoryType,
onMs: number, DefaultsType,
offMs: number, GroupAlertType,
}; Lights,
NativeAndroidNotification,
type Progress = { PriorityType,
max: number, Progress,
progress: number, SmallIcon,
indeterminate: boolean, VisibilityType,
}; } from './types';
type SmallIcon = {
icon: string,
level?: number,
};
export type NativeAndroidNotification = {|
// TODO actions: Action[],
autoCancel: boolean,
badgeIconType: BadgeIconTypeType,
category: CategoryType,
channelId: string,
clickAction?: string,
color: string,
colorized: boolean,
contentInfo: string,
defaults: DefaultsType[],
group: string,
groupAlertBehaviour: GroupAlertType,
groupSummary: boolean,
largeIcon: string,
lights: Lights,
localOnly: boolean,
number: number,
ongoing: boolean,
onlyAlertOnce: boolean,
people: string[],
priority: PriorityType,
progress: Progress,
// publicVersion: Notification,
remoteInputHistory: string[],
shortcutId: string,
showWhen: boolean,
smallIcon: SmallIcon,
sortKey: string,
// TODO: style: Style,
ticker: string,
timeoutAfter: number,
usesChronometer: boolean,
vibrate: number[],
visibility: VisibilityType,
when: number,
|};
export const BadgeIconType = {
Large: 2,
None: 0,
Small: 1,
};
export const Category = {
Alarm: 'alarm',
Call: 'call',
Email: 'email',
Error: 'err',
Event: 'event',
Message: 'msg',
Progress: 'progress',
Promo: 'promo',
Recommendation: 'recommendation',
Reminder: 'reminder',
Service: 'service',
Social: 'social',
Status: 'status',
System: 'system',
Transport: 'transport',
};
export const Defaults = {
All: -1,
Lights: 4,
Sound: 1,
Vibrate: 2,
};
export const GroupAlert = {
All: 0,
Children: 2,
Summary: 1,
};
export const Priority = {
Default: 0,
High: 1,
Low: -1,
Max: 2,
Min: -2,
};
export const Visibility = {
Private: 0,
Public: 1,
Secret: -1,
};
type BadgeIconTypeType = $Values<typeof BadgeIconType>;
type CategoryType = $Values<typeof Category>;
type DefaultsType = $Values<typeof Defaults>;
type GroupAlertType = $Values<typeof GroupAlert>;
type PriorityType = $Values<typeof Priority>;
type VisibilityType = $Values<typeof Visibility>;
export default class AndroidNotification { export default class AndroidNotification {
// TODO optional fields
// TODO actions: Action[]; // icon, title, ??pendingIntent??, allowGeneratedReplies, extender, extras, remoteinput (ugh) // TODO actions: Action[]; // icon, title, ??pendingIntent??, allowGeneratedReplies, extender, extras, remoteinput (ugh)
_autoCancel: boolean; _autoCancel: boolean;
_badgeIconType: BadgeIconTypeType; _badgeIconType: BadgeIconTypeType;
_category: CategoryType; _category: CategoryType;
_channelId: string; _channelId: string;
_clickAction: string; _clickAction: string | void;
_color: string; _color: string;
_colorized: boolean; _colorized: boolean;
_contentInfo: string; _contentInfo: string;
@ -145,9 +46,7 @@ export default class AndroidNotification {
_remoteInputHistory: string[]; _remoteInputHistory: string[];
_shortcutId: string; _shortcutId: string;
_showWhen: boolean; _showWhen: boolean;
_smallIcon: SmallIcon = { _smallIcon: SmallIcon;
icon: 'ic_launcher',
};
_sortKey: string; _sortKey: string;
// TODO: style: Style; // Need to figure out if this can work // TODO: style: Style; // Need to figure out if this can work
_ticker: string; _ticker: string;
@ -167,9 +66,50 @@ export default class AndroidNotification {
// fullScreenIntent: PendingIntent // fullScreenIntent: PendingIntent
// sound.streamType // sound.streamType
constructor(notification: Notification) { constructor(notification: Notification, data?: NativeAndroidNotification) {
this._notification = notification; this._notification = notification;
this._people = [];
if (data) {
this._autoCancel = data.autoCancel;
this._badgeIconType = data.badgeIconType;
this._category = data.category;
this._channelId = data.channelId;
this._clickAction = data.clickAction;
this._color = data.color;
this._colorized = data.colorized;
this._contentInfo = data.contentInfo;
this._defaults = data.defaults;
this._group = data.group;
this._groupAlertBehaviour = data.groupAlertBehaviour;
this._groupSummary = data.groupSummary;
this._largeIcon = data.largeIcon;
this._lights = data.lights;
this._localOnly = data.localOnly;
this._number = data.number;
this._ongoing = data.ongoing;
this._onlyAlertOnce = data.onlyAlertOnce;
this._people = data.people;
this._priority = data.priority;
this._progress = data.progress;
// _publicVersion: Notification;
this._remoteInputHistory = data.remoteInputHistory;
this._shortcutId = data.shortcutId;
this._showWhen = data.showWhen;
this._smallIcon = data.smallIcon;
this._sortKey = data.sortKey;
this._ticker = data.ticker;
this._timeoutAfter = data.timeoutAfter;
this._usesChronometer = data.usesChronometer;
this._vibrate = data.vibrate;
this._visibility = data.visibility;
this._when = data.when;
}
// Defaults
this._people = this._people || [];
this._smallIcon = this._smallIcon || {
icon: 'ic_launcher',
};
} }
/** /**
@ -506,7 +446,7 @@ export default class AndroidNotification {
} }
build(): NativeAndroidNotification { build(): NativeAndroidNotification {
// TODO: Validation // TODO: Validation of required fields
if (!this._channelId) { if (!this._channelId) {
throw new Error( throw new Error(
'AndroidNotification: Missing required `channelId` property' 'AndroidNotification: Missing required `channelId` property'

View File

@ -3,48 +3,37 @@
* IOSNotification representation wrapper * IOSNotification representation wrapper
*/ */
import type Notification from './Notification'; import type Notification from './Notification';
import type {
type AttachmentOptions = {| Attachment,
TypeHint: string, AttachmentOptions,
ThumbnailHidden: boolean, NativeIOSNotification,
ThumbnailClippingRect: { } from './types';
height: number,
width: number,
x: number,
y: number,
},
ThumbnailTime: number,
|};
type Attachment = {|
identifier: string,
options?: AttachmentOptions,
url: string,
|};
export type NativeIOSNotification = {|
alertAction?: string,
attachments: Attachment[],
badge?: number,
category?: string,
hasAction?: boolean,
launchImage?: string,
threadIdentifier?: string,
|};
export default class IOSNotification { export default class IOSNotification {
_alertAction: string; // alertAction | N/A _alertAction: string | void; // alertAction | N/A
_attachments: Attachment[]; // N/A | attachments _attachments: Attachment[]; // N/A | attachments
_badge: number; // applicationIconBadgeNumber | badge _badge: number | void; // applicationIconBadgeNumber | badge
_category: string; _category: string | void;
_hasAction: boolean; // hasAction | N/A _hasAction: boolean | void; // hasAction | N/A
_launchImage: string; // alertLaunchImage | launchImageName _launchImage: string | void; // alertLaunchImage | launchImageName
_notification: Notification; _notification: Notification;
_threadIdentifier: string; // N/A | threadIdentifier _threadIdentifier: string | void; // N/A | threadIdentifier
constructor(notification: Notification) { constructor(notification: Notification, data?: NativeIOSNotification) {
this._attachments = [];
this._notification = notification; this._notification = notification;
if (data) {
this._alertAction = data.alertAction;
this._attachments = data.attachments;
this._badge = data.badge;
this._category = data.category;
this._hasAction = data.hasAction;
this._launchImage = data.launchImage;
this._threadIdentifier = data.threadIdentifier;
}
// Defaults
this._attachments = this._attachments || [];
} }
/** /**
@ -128,7 +117,7 @@ export default class IOSNotification {
} }
build(): NativeIOSNotification { build(): NativeIOSNotification {
// TODO: Validation // TODO: Validation of required fields
return { return {
alertAction: this._alertAction, alertAction: this._alertAction,

View File

@ -7,21 +7,7 @@ import AndroidNotification from './AndroidNotification';
import IOSNotification from './IOSNotification'; import IOSNotification from './IOSNotification';
import { generatePushID, isObject } from '../../utils'; import { generatePushID, isObject } from '../../utils';
import type { NativeAndroidNotification } from './AndroidNotification'; import type { NativeNotification } from './types';
import type { NativeIOSNotification } from './IOSNotification';
import type { Schedule } from './';
type NativeNotification = {|
android?: NativeAndroidNotification,
body: string,
data: { [string]: string },
ios?: NativeIOSNotification,
notificationId: string,
schedule?: Schedule,
sound?: string,
subtitle?: string,
title: string,
|};
export default class Notification { export default class Notification {
// iOS 8/9 | 10+ | Android // iOS 8/9 | 10+ | Android
@ -34,12 +20,23 @@ export default class Notification {
_subtitle: string | void; // N/A | subtitle | subText _subtitle: string | void; // N/A | subtitle | subText
_title: string; // alertTitle | title | contentTitle _title: string; // alertTitle | title | contentTitle
constructor() { constructor(data?: NativeNotification) {
this._android = new AndroidNotification(this); this._android = new AndroidNotification(this, data && data.android);
this._data = {}; this._ios = new IOSNotification(this, data && data.ios);
this._ios = new IOSNotification(this);
// TODO: Is this the best way to generate an ID? if (data) {
this._notificationId = generatePushID(); this._body = data.body;
this._data = data.data;
// TODO: Is this the best way to generate an ID?
this._notificationId = data.notificationId;
this._sound = data.sound;
this._subtitle = data.subtitle;
this._title = data.title;
}
// Defaults
this._data = this._data || {};
this._notificationId = this._notificationId || generatePushID();
} }
get android(): AndroidNotification { get android(): AndroidNotification {

View File

@ -15,26 +15,20 @@ import {
GroupAlert, GroupAlert,
Priority, Priority,
Visibility, Visibility,
} from './AndroidNotification'; } from './types';
import type App from '../core/app'; import type App from '../core/app';
import type { NativeNotification, Schedule } from './types';
// TODO: Received notification type will be different from sent notification
type OnNotification = Notification => any; type OnNotification = Notification => any;
type OnNotificationObserver = { type OnNotificationObserver = {
next: OnNotification, next: OnNotification,
}; };
export type Schedule = {
exact?: boolean,
fireDate: number,
repeatInterval?: 'minute' | 'hour' | 'day' | 'week',
};
const NATIVE_EVENTS = [ const NATIVE_EVENTS = [
'notifications_notification_clicked',
'notifications_notification_displayed', 'notifications_notification_displayed',
'notifications_notification_pressed',
'notifications_notification_received', 'notifications_notification_received',
]; ];
@ -69,10 +63,13 @@ export default class Notifications extends ModuleBase {
SharedEventEmitter.addListener( SharedEventEmitter.addListener(
// sub to internal native event - this fans out to // sub to internal native event - this fans out to
// public event name: onNotificationClicked // public event name: onNotificationPressed
'notifications_notification_clicked', 'notifications_notification_pressed',
(notification: Notification) => { (notification: NativeNotification) => {
SharedEventEmitter.emit('onNotificationClicked', notification); SharedEventEmitter.emit(
'onNotificationPressed',
new Notification(notification)
);
} }
); );
@ -80,8 +77,11 @@ export default class Notifications extends ModuleBase {
// sub to internal native event - this fans out to // sub to internal native event - this fans out to
// public event name: onNotificationDisplayed // public event name: onNotificationDisplayed
'notifications_notification_displayed', 'notifications_notification_displayed',
(notification: Notification) => { (notification: NativeNotification) => {
SharedEventEmitter.emit('onNotificationDisplayed', notification); SharedEventEmitter.emit(
'onNotificationDisplayed',
new Notification(notification)
);
} }
); );
@ -89,8 +89,11 @@ export default class Notifications extends ModuleBase {
// sub to internal native event - this fans out to // sub to internal native event - this fans out to
// public event name: onNotification // public event name: onNotification
'notifications_notification_received', 'notifications_notification_received',
(notification: Notification) => { (notification: NativeNotification) => {
SharedEventEmitter.emit('onNotification', notification); SharedEventEmitter.emit(
'onNotification',
new Notification(notification)
);
} }
); );
} }
@ -166,29 +169,6 @@ export default class Notifications extends ModuleBase {
}; };
} }
onNotificationClicked(
nextOrObserver: OnNotification | OnNotificationObserver
): () => any {
let listener;
if (isFunction(nextOrObserver)) {
listener = nextOrObserver;
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
listener = nextOrObserver.next;
} else {
throw new Error(
'Notifications.onNotificationClicked failed: First argument must be a function or observer object with a `next` function.'
);
}
getLogger(this).info('Creating onNotificationClicked listener');
SharedEventEmitter.addListener('onNotificationClicked', listener);
return () => {
getLogger(this).info('Removing onNotificationClicked listener');
SharedEventEmitter.removeListener('onNotificationClicked', listener);
};
}
onNotificationDisplayed( onNotificationDisplayed(
nextOrObserver: OnNotification | OnNotificationObserver nextOrObserver: OnNotification | OnNotificationObserver
): () => any { ): () => any {
@ -212,6 +192,30 @@ export default class Notifications extends ModuleBase {
}; };
} }
onNotificationPressed(
nextOrObserver: OnNotification | OnNotificationObserver
): () => any {
let listener: Notification => any;
if (isFunction(nextOrObserver)) {
// $FlowBug: Not coping with the overloaded method signature
listener = nextOrObserver;
} else if (isObject(nextOrObserver) && isFunction(nextOrObserver.next)) {
listener = nextOrObserver.next;
} else {
throw new Error(
'Notifications.onNotificationPressed failed: First argument must be a function or observer object with a `next` function.'
);
}
getLogger(this).info('Creating onNotificationPressed listener');
SharedEventEmitter.addListener('onNotificationPressed', listener);
return () => {
getLogger(this).info('Removing onNotificationPressed listener');
SharedEventEmitter.removeListener('onNotificationPressed', listener);
};
}
/** /**
* Remove all delivered notifications. * Remove all delivered notifications.
* @returns {*} * @returns {*}

View File

@ -0,0 +1,162 @@
/**
* @flow
*/
export const BadgeIconType = {
Large: 2,
None: 0,
Small: 1,
};
export const Category = {
Alarm: 'alarm',
Call: 'call',
Email: 'email',
Error: 'err',
Event: 'event',
Message: 'msg',
Progress: 'progress',
Promo: 'promo',
Recommendation: 'recommendation',
Reminder: 'reminder',
Service: 'service',
Social: 'social',
Status: 'status',
System: 'system',
Transport: 'transport',
};
export const Defaults = {
All: -1,
Lights: 4,
Sound: 1,
Vibrate: 2,
};
export const GroupAlert = {
All: 0,
Children: 2,
Summary: 1,
};
export const Priority = {
Default: 0,
High: 1,
Low: -1,
Max: 2,
Min: -2,
};
export const Visibility = {
Private: 0,
Public: 1,
Secret: -1,
};
export type BadgeIconTypeType = $Values<typeof BadgeIconType>;
export type CategoryType = $Values<typeof Category>;
export type DefaultsType = $Values<typeof Defaults>;
export type GroupAlertType = $Values<typeof GroupAlert>;
export type PriorityType = $Values<typeof Priority>;
export type VisibilityType = $Values<typeof Visibility>;
export type Lights = {
argb: number,
onMs: number,
offMs: number,
};
export type Progress = {
max: number,
progress: number,
indeterminate: boolean,
};
export type SmallIcon = {
icon: string,
level?: number,
};
export type NativeAndroidNotification = {|
// TODO actions: Action[],
autoCancel: boolean,
badgeIconType: BadgeIconTypeType,
category: CategoryType,
channelId: string,
clickAction?: string,
color: string,
colorized: boolean,
contentInfo: string,
defaults: DefaultsType[],
group: string,
groupAlertBehaviour: GroupAlertType,
groupSummary: boolean,
largeIcon: string,
lights: Lights,
localOnly: boolean,
number: number,
ongoing: boolean,
onlyAlertOnce: boolean,
people: string[],
priority: PriorityType,
progress: Progress,
// publicVersion: Notification,
remoteInputHistory: string[],
shortcutId: string,
showWhen: boolean,
smallIcon: SmallIcon,
sortKey: string,
// TODO: style: Style,
ticker: string,
timeoutAfter: number,
usesChronometer: boolean,
vibrate: number[],
visibility: VisibilityType,
when: number,
|};
export type AttachmentOptions = {|
TypeHint: string,
ThumbnailHidden: boolean,
ThumbnailClippingRect: {
height: number,
width: number,
x: number,
y: number,
},
ThumbnailTime: number,
|};
export type Attachment = {|
identifier: string,
options?: AttachmentOptions,
url: string,
|};
export type NativeIOSNotification = {|
alertAction?: string,
attachments: Attachment[],
badge?: number,
category?: string,
hasAction?: boolean,
launchImage?: string,
threadIdentifier?: string,
|};
export type Schedule = {
exact?: boolean,
fireDate: number,
repeatInterval?: 'minute' | 'hour' | 'day' | 'week',
};
export type NativeNotification = {|
android?: NativeAndroidNotification,
body: string,
data: { [string]: string },
ios?: NativeIOSNotification,
notificationId: string,
schedule?: Schedule,
sound?: string,
subtitle?: string,
title: string,
|};