From a6c63d4f50551eb1dc90a5825ab0b5cca44250f2 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 30 Mar 2018 09:25:41 +0100 Subject: [PATCH 1/4] [links] Introduce JS initialised method to improve getInitialLink --- ios/RNFirebase/links/RNFirebaseLinks.m | 37 +++++++++++++++++++++----- lib/modules/links/index.js | 6 +++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/ios/RNFirebase/links/RNFirebaseLinks.m b/ios/RNFirebase/links/RNFirebaseLinks.m index 63ce16c8..47d30a8a 100644 --- a/ios/RNFirebase/links/RNFirebaseLinks.m +++ b/ios/RNFirebase/links/RNFirebaseLinks.m @@ -8,8 +8,16 @@ @implementation RNFirebaseLinks static RNFirebaseLinks *theRNFirebaseLinks = nil; +static NSString *initialLink = nil; +static bool jsReady = FALSE; + (nonnull instancetype)instance { + // If an event comes in before the bridge has initialised the native module + // then we create a temporary instance which handles events until the bridge + // and JS side are ready + if (theRNFirebaseLinks == nil) { + theRNFirebaseLinks = [[RNFirebaseLinks alloc] init]; + } return theRNFirebaseLinks; } @@ -39,7 +47,7 @@ RCT_EXPORT_MODULE(); FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url]; if (dynamicLink && dynamicLink.url) { NSURL* url = dynamicLink.url; - [RNFirebaseUtil sendJSEvent:self name:LINKS_LINK_RECEIVED body:url.absoluteString]; + [self sendJSEvent:self name:LINKS_LINK_RECEIVED body:url.absoluteString]; return YES; } return NO; @@ -56,7 +64,7 @@ continueUserActivity:(NSUserActivity *)userActivity NSLog(@"Failed to handle universal link: %@", [error localizedDescription]); } else { NSURL* url = dynamicLink ? dynamicLink.url : userActivity.webpageURL; - [RNFirebaseUtil sendJSEvent:self name:LINKS_LINK_RECEIVED body:url.absoluteString]; + [self sendJSEvent:self name:LINKS_LINK_RECEIVED body:url.absoluteString]; } }]; } @@ -67,7 +75,7 @@ continueUserActivity:(NSUserActivity *)userActivity // ******************************************************* - (void)sendLink:(NSString *)link { - [RNFirebaseUtil sendJSEvent:self name:LINKS_LINK_RECEIVED body:link]; + [self sendJSEvent:self name:LINKS_LINK_RECEIVED body:link]; } // ** Start React Module methods ** @@ -127,7 +135,7 @@ RCT_EXPORT_METHOD(getInitialLink:(RCTPromiseResolveBlock)resolve rejecter:(RCTPr if (self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) { NSURL* url = (NSURL*)self.bridge.launchOptions[UIApplicationLaunchOptionsURLKey]; FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url]; - resolve(dynamicLink ? dynamicLink.url.absoluteString : nil); + resolve(dynamicLink ? dynamicLink.url.absoluteString : initialLink); } else if (self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey] && [self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey][UIApplicationLaunchOptionsUserActivityTypeKey] isEqualToString:NSUserActivityTypeBrowsingWeb]) { NSDictionary *dictionary = self.bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]; @@ -144,11 +152,29 @@ RCT_EXPORT_METHOD(getInitialLink:(RCTPromiseResolveBlock)resolve rejecter:(RCTPr } }]; } else { - resolve(nil); + resolve(initialLink); } } +RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + jsReady = TRUE; + resolve(nil); +} + // ** Start internals ** + +// Because of the time delay between the app starting and the bridge being initialised +// we catch any events that are received before the JS is ready to receive them +- (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body { + if (emitter.bridge && jsReady) { + [RNFirebaseUtil sendJSEvent:emitter name:name body:body]; + } else if (!initialLink) { + initialLink = body; + } else { + NSLog(@"Multiple link events received before the JS links modules has been initialised"); + } +} + - (FIRDynamicLinkComponents *)buildDynamicLink:(NSDictionary *)linkData { @try { NSURL *link = [NSURL URLWithString:linkData[@"link"]]; @@ -285,4 +311,3 @@ RCT_EXPORT_METHOD(getInitialLink:(RCTPromiseResolveBlock)resolve rejecter:(RCTPr @implementation RNFirebaseLinks @end #endif - diff --git a/lib/modules/links/index.js b/lib/modules/links/index.js index 1a744327..54c47dd1 100644 --- a/lib/modules/links/index.js +++ b/lib/modules/links/index.js @@ -2,6 +2,7 @@ * @flow * Dynamic Links representation wrapper */ +import { Platform } from 'react-native'; import DynamicLink from './DynamicLink'; import { SharedEventEmitter } from '../../utils/events'; import { getLogger } from '../../utils/log'; @@ -36,6 +37,11 @@ export default class Links extends ModuleBase { SharedEventEmitter.emit('onLink', link); } ); + + // Tell the native module that we're ready to receive events + if (Platform.os === 'ios') { + getNativeModule(this).jsInitialised(); + } } /** From e7be3cc7f841c4169fa677ba8d014fb4057508ca Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 30 Mar 2018 09:52:37 +0100 Subject: [PATCH 2/4] [links] Correct platform OS name --- lib/modules/links/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/links/index.js b/lib/modules/links/index.js index 54c47dd1..5fca4884 100644 --- a/lib/modules/links/index.js +++ b/lib/modules/links/index.js @@ -39,7 +39,7 @@ export default class Links extends ModuleBase { ); // Tell the native module that we're ready to receive events - if (Platform.os === 'ios') { + if (Platform.OS === 'ios') { getNativeModule(this).jsInitialised(); } } From 06c06b764f8289c527588db1f0b34a418f1dfbcb Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 30 Mar 2018 10:07:23 +0100 Subject: [PATCH 3/4] [invites] Add jsInitialised method to improve getInitialInvitation --- ios/RNFirebase/invites/RNFirebaseInvites.m | 36 ++++++++++++++++++---- ios/RNFirebase/links/RNFirebaseLinks.m | 2 +- lib/modules/invites/index.js | 6 ++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/ios/RNFirebase/invites/RNFirebaseInvites.m b/ios/RNFirebase/invites/RNFirebaseInvites.m index 98f47561..f6c85ffd 100644 --- a/ios/RNFirebase/invites/RNFirebaseInvites.m +++ b/ios/RNFirebase/invites/RNFirebaseInvites.m @@ -13,8 +13,16 @@ @implementation RNFirebaseInvites static RNFirebaseInvites *theRNFirebaseInvites = nil; +static NSString *initialInvite = nil; +static bool jsReady = FALSE; + (nonnull instancetype)instance { + // If an event comes in before the bridge has initialised the native module + // then we create a temporary instance which handles events until the bridge + // and JS side are ready + if (theRNFirebaseInvites == nil) { + theRNFirebaseInvites = [[RNFirebaseInvites alloc] init]; + } return theRNFirebaseInvites; } @@ -101,11 +109,11 @@ RCT_EXPORT_METHOD(getInitialInvitation:(RCTPromiseResolveBlock)resolve rejecter: @"invitationId": receivedInvite.inviteId, }); } else { - resolve(nil); + resolve(initialInvite); } }]; } else { - resolve(nil); + resolve(initialInvite); } } @@ -151,22 +159,38 @@ RCT_EXPORT_METHOD(sendInvitation:(NSDictionary *) invitation }); } +RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + jsReady = TRUE; + resolve(nil); +} + // ** Start internals ** - (BOOL)handleUrl:(NSURL *)url { return [FIRInvites handleUniversalLink:url completion:^(FIRReceivedInvite * _Nullable receivedInvite, NSError * _Nullable error) { if (error) { NSLog(@"Failed to handle invitation: %@", [error localizedDescription]); } else if (receivedInvite && receivedInvite.inviteId) { - [RNFirebaseUtil sendJSEvent:self name:INVITES_INVITATION_RECEIVED body:@{ - @"deepLink": receivedInvite.deepLink, - @"invitationId": receivedInvite.inviteId, - }]; + [self sendJSEvent:self name:INVITES_INVITATION_RECEIVED body:@{ + @"deepLink": receivedInvite.deepLink, + @"invitationId": receivedInvite.inviteId, + }]; } else { [[RNFirebaseLinks instance] sendLink:receivedInvite.deepLink]; } }]; } +// Because of the time delay between the app starting and the bridge being initialised +// we catch any events that are received before the JS is ready to receive them +- (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body { + if (emitter.bridge && jsReady) { + [RNFirebaseUtil sendJSEvent:emitter name:name body:body]; + } else if (!initialInvite) { + initialInvite = body; + } else { + NSLog(@"Multiple invite events received before the JS invites module has been initialised"); + } +} - (NSArray *)supportedEvents { return @[INVITES_INVITATION_RECEIVED]; diff --git a/ios/RNFirebase/links/RNFirebaseLinks.m b/ios/RNFirebase/links/RNFirebaseLinks.m index 47d30a8a..db3a94ea 100644 --- a/ios/RNFirebase/links/RNFirebaseLinks.m +++ b/ios/RNFirebase/links/RNFirebaseLinks.m @@ -171,7 +171,7 @@ RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPro } else if (!initialLink) { initialLink = body; } else { - NSLog(@"Multiple link events received before the JS links modules has been initialised"); + NSLog(@"Multiple link events received before the JS links module has been initialised"); } } diff --git a/lib/modules/invites/index.js b/lib/modules/invites/index.js index a9246c03..31f81df9 100644 --- a/lib/modules/invites/index.js +++ b/lib/modules/invites/index.js @@ -2,6 +2,7 @@ * @flow * Invites representation wrapper */ +import { Platform } from 'react-native'; import { SharedEventEmitter } from '../../utils/events'; import { getLogger } from '../../utils/log'; import ModuleBase from '../../utils/ModuleBase'; @@ -37,6 +38,11 @@ export default class Invites extends ModuleBase { SharedEventEmitter.emit('onInvitation', invitation); } ); + + // Tell the native module that we're ready to receive events + if (Platform.OS === 'ios') { + getNativeModule(this).jsInitialised(); + } } /** From 801adabb27d0684781dc32086e2ef95f33556ae4 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 30 Mar 2018 10:31:06 +0100 Subject: [PATCH 4/4] [notifications] Add jsInitialised method to improve getInitialNotification --- .../notifications/RNFirebaseNotifications.m | 46 ++++++++++--------- lib/modules/notifications/index.js | 6 +++ 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/ios/RNFirebase/notifications/RNFirebaseNotifications.m b/ios/RNFirebase/notifications/RNFirebaseNotifications.m index eaf1afc3..160659e8 100644 --- a/ios/RNFirebase/notifications/RNFirebaseNotifications.m +++ b/ios/RNFirebase/notifications/RNFirebaseNotifications.m @@ -22,6 +22,7 @@ static RNFirebaseNotifications *theRNFirebaseNotifications = nil; // PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side // static NSMutableArray *pendingEvents = nil; static NSDictionary *initialNotification = nil; +static bool jsReady = FALSE; + (nonnull instancetype)instance { return theRNFirebaseNotifications; @@ -266,27 +267,23 @@ RCT_EXPORT_METHOD(getBadge: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromise } RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - if ([self isIOS89]) { - // With iOS 8/9 we rely on the launch options - UILocalNotification *localNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]; - NSDictionary *notification; - if (localNotification) { - notification = [self parseUILocalNotification:localNotification]; - } else { - NSDictionary *remoteNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; - if (remoteNotification) { - notification = [self parseUserInfo:remoteNotification]; - } - } - if (notification) { - resolve(@{@"action": UNNotificationDefaultActionIdentifier, @"notification": notification}); - } else { - resolve(nil); - } - } else { - // With iOS 10+ we are able to intercept the didReceiveNotificationResponse message - // to get both the notification and the action + // Check if we've cached an initial notification as this will contain the accurate action + if (initialNotification) { resolve(initialNotification); + } else if (self.bridge.launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) { + UILocalNotification *localNotification = self.bridge.launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]; + resolve(@{ + @"action": UNNotificationDefaultActionIdentifier, + @"notification": [self parseUILocalNotification:localNotification] + }); + } else if (self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) { + NSDictionary *remoteNotification = [self bridge].launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; + resolve(@{ + @"action": UNNotificationDefaultActionIdentifier, + @"notification": [self parseUserInfo:remoteNotification] + }); + } else { + resolve(nil); } } @@ -373,16 +370,23 @@ RCT_EXPORT_METHOD(setBadge:(NSInteger) number resolve(nil); }); } + +RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + jsReady = TRUE; + resolve(nil); +} // Because of the time delay between the app starting and the bridge being initialised // we create a temporary instance of RNFirebaseNotifications. // With this temporary instance, we cache any events to be sent as soon as the bridge is set on the module - (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body { - if (emitter.bridge) { + if (emitter.bridge && jsReady) { [RNFirebaseUtil sendJSEvent:emitter name:name body:body]; } else { if ([name isEqualToString:NOTIFICATIONS_NOTIFICATION_OPENED] && !initialNotification) { initialNotification = body; + } else if ([name isEqualToString:NOTIFICATIONS_NOTIFICATION_OPENED]) { + NSLog(@"Multiple notification open events received before the JS Notifications module has been initialised"); } // PRE-BRIDGE-EVENTS: Consider enabling this to allow events built up before the bridge is built to be sent to the JS side // [pendingEvents addObject:@{@"name":name, @"body":body}]; diff --git a/lib/modules/notifications/index.js b/lib/modules/notifications/index.js index 7cbd72aa..b42d3d65 100644 --- a/lib/modules/notifications/index.js +++ b/lib/modules/notifications/index.js @@ -2,6 +2,7 @@ * @flow * Notifications representation wrapper */ +import { Platform } from 'react-native'; import { SharedEventEmitter } from '../../utils/events'; import { getLogger } from '../../utils/log'; import ModuleBase from '../../utils/ModuleBase'; @@ -119,6 +120,11 @@ export default class Notifications extends ModuleBase { ); } ); + + // Tell the native module that we're ready to receive events + if (Platform.OS === 'ios') { + getNativeModule(this).jsInitialised(); + } } get android(): AndroidNotifications {