diff options
author | Xiangtian Dai <xiangtian@google.com> | 2017-08-21 21:14:20 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-21 21:14:20 -0700 |
commit | 6e12c501f8dde3057d26149826e82489e114b2a1 (patch) | |
tree | 21145365c0433793ed1f59240db8815812d24fce | |
parent | 2c19745fb8f263730a390bb959b00b4209e00916 (diff) |
Forwards app delegate's openURL invocations to `FIRAuth`. (#207)
-rw-r--r-- | Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m | 256 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuth.m | 4 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuthAppDelegateProxy.h | 7 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuthAppDelegateProxy.m | 128 | ||||
-rw-r--r-- | Firebase/Auth/Source/Public/FIRAuth.h | 12 |
5 files changed, 385 insertions, 22 deletions
diff --git a/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m b/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m index 9ff7473..b65de93 100644 --- a/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m +++ b/Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m @@ -33,7 +33,8 @@ NS_ASSUME_NONNULL_BEGIN /** @class FIRAuthLegacyAppDelegate @brief A @c UIApplicationDelegate implementation that implements - `application:didReceiveRemoteNotification:`. + `application:didReceiveRemoteNotification:` and + `application:openURL:sourceApplication:annotation:`. */ @interface FIRAuthLegacyAppDelegate : NSObject <UIApplicationDelegate> @@ -42,6 +43,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, copy, nullable) NSDictionary *notificationReceived; +/** @var urlOpened + @brief The last URL opened, if any. + */ +@property(nonatomic, copy, nullable) NSURL *urlOpened; + @end @implementation FIRAuthLegacyAppDelegate @@ -51,12 +57,21 @@ NS_ASSUME_NONNULL_BEGIN self.notificationReceived = userInfo; } +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(nullable NSString *)sourceApplication + annotation:(id)annotation { + self.urlOpened = url; + return NO; +} + @end /** @class FIRAuthModernAppDelegate @brief A @c UIApplicationDelegate implementation that implements both - `application:didRegisterForRemoteNotificationsWithDeviceToken:` and - `application:didReceiveRemoteNotification:fetchCompletionHandler:`. + `application:didRegisterForRemoteNotificationsWithDeviceToken:`, + `application:didReceiveRemoteNotification:fetchCompletionHandler:`, and + `application:openURL:options:`. */ @interface FIRAuthModernAppDelegate : NSObject <UIApplicationDelegate> @@ -70,6 +85,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, copy, nullable) NSDictionary *notificationReceived; +/** @var urlOpened + @brief The last URL opened, if any. + */ +@property(nonatomic, copy, nullable) NSURL *urlOpened; + @end @implementation FIRAuthModernAppDelegate @@ -86,6 +106,34 @@ NS_ASSUME_NONNULL_BEGIN completionHandler(UIBackgroundFetchResultNewData); } +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { + self.urlOpened = url; + return NO; +} + +@end + +/** @class FIRAuthOtherLegacyAppDelegate + @brief A @c UIApplicationDelegate implementation that implements `application:handleOpenURL:`. + */ +@interface FIRAuthOtherLegacyAppDelegate : NSObject <UIApplicationDelegate> + +/** @var urlOpened + @brief The last URL opened, if any. + */ +@property(nonatomic, copy, nullable) NSURL *urlOpened; + +@end + +@implementation FIRAuthOtherLegacyAppDelegate + +- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url { + self.urlOpened = url; + return NO; +} + @end /** @class FIRAuthAppDelegateProxyTests @@ -108,6 +156,11 @@ NS_ASSUME_NONNULL_BEGIN @brief The fake notification for testing. */ NSDictionary* _notification; + + /** @var _url + @brief The fake URL for testing. + */ + NSURL *_url; } - (void)setUp { @@ -115,6 +168,7 @@ NS_ASSUME_NONNULL_BEGIN _mockApplication = OCMClassMock([UIApplication class]); _deviceToken = [@"asdf" dataUsingEncoding:NSUTF8StringEncoding]; _notification = @{ @"zxcv" : @1234 }; + _url = [NSURL URLWithString:@"https://abc.def/ghi"]; } - (void)tearDown { @@ -193,9 +247,23 @@ NS_ASSUME_NONNULL_BEGIN [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; XCTAssertNotNil(proxy); - // Verify `application:didReceiveRemoteNotification:` is not swizzled. + // Verify certain methods are swizzled while others are not. + XCTAssertTrue([delegate respondsToSelector: + @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); + XCTAssertTrue([delegate respondsToSelector: + @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]); XCTAssertFalse([delegate respondsToSelector: @selector(application:didReceiveRemoteNotification:)]); + if (&UIApplicationOpenURLOptionsAnnotationKey) { // iOS 9+ + XCTAssertTrue([delegate respondsToSelector:@selector(application:openURL:options:)]); + XCTAssertFalse([delegate respondsToSelector: + @selector(application:openURL:sourceApplication:annotation:)]); + } else { + XCTAssertFalse([delegate respondsToSelector:@selector(application:openURL:options:)]); + XCTAssertTrue([delegate respondsToSelector: + @selector(application:openURL:sourceApplication:annotation:)]); + } + XCTAssertFalse([delegate respondsToSelector:@selector(application:handleOpenURL:)]); // Verify the handler is called after being added. __weak id weakHandler; @@ -203,13 +271,13 @@ NS_ASSUME_NONNULL_BEGIN id mockHandler = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler)); [proxy addHandler:mockHandler]; - // Verify handling of `application:didRegisterForRemoteNotificationsWithDeviceToken:`. + // Verify `application:didRegisterForRemoteNotificationsWithDeviceToken:` is handled. OCMExpect([mockHandler setAPNSToken:_deviceToken]); [delegate application:_mockApplication didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; OCMVerifyAll(mockHandler); - // Verify handling of `application:didReceiveRemoteNotification:fetchCompletionHandler:`. + // Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is handled. OCMExpect([mockHandler canHandleNotification:_notification]).andReturn(YES); __block BOOL fetchCompletionHandlerCalled = NO; [delegate application:_mockApplication @@ -221,6 +289,20 @@ NS_ASSUME_NONNULL_BEGIN OCMVerifyAll(mockHandler); XCTAssertTrue(fetchCompletionHandlerCalled); + // Verify one of the `application:openURL:...` methods is handled. + OCMExpect([mockHandler canHandleURL:_url]).andReturn(YES); + if (&UIApplicationOpenURLOptionsAnnotationKey) { // iOS 9+ + // Verify `application:openURL:options:` is handled. + XCTAssertTrue([delegate application:_mockApplication openURL:_url options:@{}]); + } else { + // Verify `application:openURL:sourceApplication:annotation:` is handled. + XCTAssertTrue([delegate application:_mockApplication + openURL:_url + sourceApplication:@"sourceApplication" + annotation:@"annotaton"]); + } + OCMVerifyAll(mockHandler); + weakHandler = mockHandler; XCTAssertNotNil(weakHandler); } @@ -235,7 +317,14 @@ NS_ASSUME_NONNULL_BEGIN fetchCompletionHandler:^(UIBackgroundFetchResult result) { XCTFail(@"Should not call completion handler."); }]; - + if (&UIApplicationOpenURLOptionsAnnotationKey) { // iOS 9+ + XCTAssertFalse([delegate application:_mockApplication openURL:_url options:@{}]); + } else { + XCTAssertFalse([delegate application:_mockApplication + openURL:_url + sourceApplication:@"sourceApplication" + annotation:@"annotaton"]); + } weakProxy = proxy; XCTAssertNotNil(weakProxy); } @@ -249,6 +338,14 @@ NS_ASSUME_NONNULL_BEGIN fetchCompletionHandler:^(UIBackgroundFetchResult result) { XCTFail(@"Should not call completion handler."); }]; + if (&UIApplicationOpenURLOptionsAnnotationKey) { // iOS 9+ + XCTAssertFalse([delegate application:_mockApplication openURL:_url options:@{}]); + } else { + XCTAssertFalse([delegate application:_mockApplication + openURL:_url + sourceApplication:@"sourceApplication" + annotation:@"annotaton"]); + } } /** @fn testLegacyDelegateTwoHandlers @@ -263,9 +360,17 @@ NS_ASSUME_NONNULL_BEGIN [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; XCTAssertNotNil(proxy); - // Verify `application:didReceiveRemoteNotification:fetchCompletionHandler` is not swizzled. + // Verify certain methods are swizzled while others are not. + XCTAssertTrue([delegate respondsToSelector: + @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); XCTAssertFalse([delegate respondsToSelector: @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]); + XCTAssertTrue([delegate respondsToSelector: + @selector(application:didReceiveRemoteNotification:)]); + XCTAssertFalse([delegate respondsToSelector:@selector(application:openURL:options:)]); + XCTAssertTrue([delegate respondsToSelector: + @selector(application:openURL:sourceApplication:annotation:)]); + XCTAssertFalse([delegate respondsToSelector:@selector(application:handleOpenURL:)]); // Verify the handler is called after being added. __weak id weakHandler1; @@ -277,7 +382,7 @@ NS_ASSUME_NONNULL_BEGIN id mockHandler2 = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler)); [proxy addHandler:mockHandler2]; - // Verify handling of `application:didRegisterForRemoteNotificationsWithDeviceToken:`. + // Verify `application:didRegisterForRemoteNotificationsWithDeviceToken:` is handled. OCMExpect([mockHandler1 setAPNSToken:_deviceToken]); OCMExpect([mockHandler2 setAPNSToken:_deviceToken]); [delegate application:_mockApplication @@ -285,7 +390,7 @@ NS_ASSUME_NONNULL_BEGIN OCMVerifyAll(mockHandler1); OCMVerifyAll(mockHandler2); - // Verify handling of `application:didReceiveRemoteNotification:fetchCompletionHandler:`. + // Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is handled. OCMExpect([mockHandler1 canHandleNotification:_notification]).andReturn(YES); // handler2 shouldn't been invoked because it is already handled by handler1. [delegate application:_mockApplication didReceiveRemoteNotification:_notification]; @@ -293,25 +398,45 @@ NS_ASSUME_NONNULL_BEGIN OCMVerifyAll(mockHandler2); XCTAssertNil(delegate.notificationReceived); + // Verify `application:openURL:sourceApplication:annotation:` is handled. + OCMExpect([mockHandler1 canHandleURL:_url]).andReturn(YES); + XCTAssertTrue([delegate application:_mockApplication + openURL:_url + sourceApplication:@"sourceApplication" + annotation:@"annotaton"]); + OCMVerifyAll(mockHandler1); + OCMVerifyAll(mockHandler2); + XCTAssertNil(delegate.urlOpened); + weakHandler2 = mockHandler2; XCTAssertNotNil(weakHandler2); } // Verify the handler2 is not retained by the proxy. XCTAssertNil(weakHandler2); - // Verify handling of `application:didRegisterForRemoteNotificationsWithDeviceToken:`. + // Verify `application:didRegisterForRemoteNotificationsWithDeviceToken:` is handled. OCMExpect([mockHandler1 setAPNSToken:_deviceToken]); [delegate application:_mockApplication didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; OCMVerifyAll(mockHandler1); - // Verify NOT handling of `application:didReceiveRemoteNotification:fetchCompletionHandler:`. + // Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is NOT handled. OCMExpect([mockHandler1 canHandleNotification:_notification]).andReturn(NO); [delegate application:_mockApplication didReceiveRemoteNotification:_notification]; OCMVerifyAll(mockHandler1); XCTAssertEqualObjects(delegate.notificationReceived, _notification); delegate.notificationReceived = nil; + // Verify `application:openURL:sourceApplication:annotation:` is NOT handled. + OCMExpect([mockHandler1 canHandleURL:_url]).andReturn(NO); + XCTAssertFalse([delegate application:_mockApplication + openURL:_url + sourceApplication:@"sourceApplication" + annotation:@"annotation"]); + OCMVerifyAll(mockHandler1); + XCTAssertEqualObjects(delegate.urlOpened, _url); + delegate.urlOpened = nil; + weakHandler1 = mockHandler1; XCTAssertNotNil(weakHandler1); } @@ -324,6 +449,12 @@ NS_ASSUME_NONNULL_BEGIN [delegate application:_mockApplication didReceiveRemoteNotification:_notification]; XCTAssertEqualObjects(delegate.notificationReceived, _notification); delegate.notificationReceived = nil; + XCTAssertFalse([delegate application:_mockApplication + openURL:_url + sourceApplication:@"sourceApplication" + annotation:@"annotation"]); + XCTAssertEqualObjects(delegate.urlOpened, _url); + delegate.urlOpened = nil; weakProxy = proxy; XCTAssertNotNil(weakProxy); @@ -337,6 +468,12 @@ NS_ASSUME_NONNULL_BEGIN [delegate application:_mockApplication didReceiveRemoteNotification:_notification]; XCTAssertEqualObjects(delegate.notificationReceived, _notification); delegate.notificationReceived = nil; + XCTAssertFalse([delegate application:_mockApplication + openURL:_url + sourceApplication:@"sourceApplication" + annotation:@"annotation"]); + XCTAssertEqualObjects(delegate.urlOpened, _url); + delegate.urlOpened = nil; } /** @fn testModernDelegateWithOtherInstance @@ -353,9 +490,22 @@ NS_ASSUME_NONNULL_BEGIN [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; XCTAssertNotNil(proxy); - // Verify `application:didReceiveRemoteNotification:` is not swizzled. + // Verify certain methods are swizzled while others are not. + XCTAssertTrue([delegate respondsToSelector: + @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]); + XCTAssertTrue([delegate respondsToSelector: + @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]); XCTAssertFalse([delegate respondsToSelector: @selector(application:didReceiveRemoteNotification:)]); + XCTAssertTrue([delegate respondsToSelector:@selector(application:openURL:options:)]); + if (&UIApplicationOpenURLOptionsAnnotationKey) { // iOS 9+ + XCTAssertFalse([delegate respondsToSelector: + @selector(application:openURL:sourceApplication:annotation:)]); + } else { + XCTAssertTrue([delegate respondsToSelector: + @selector(application:openURL:sourceApplication:annotation:)]); + } + XCTAssertFalse([delegate respondsToSelector:@selector(application:handleOpenURL:)]); // Verify the handler is called after being added. __weak id weakHandler; @@ -363,7 +513,7 @@ NS_ASSUME_NONNULL_BEGIN id mockHandler = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler)); [proxy addHandler:mockHandler]; - // Verify handling of `application:didRegisterForRemoteNotificationsWithDeviceToken:`. + // Verify `application:didRegisterForRemoteNotificationsWithDeviceToken:` is handled. OCMExpect([mockHandler setAPNSToken:_deviceToken]); [delegate application:_mockApplication didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; @@ -371,7 +521,7 @@ NS_ASSUME_NONNULL_BEGIN XCTAssertEqualObjects(delegate.deviceTokenReceived, _deviceToken); delegate.deviceTokenReceived = nil; - // Verify handling of `application:didReceiveRemoteNotification:fetchCompletionHandler:`. + // Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is handled. OCMExpect([mockHandler canHandleNotification:_notification]).andReturn(YES); __block BOOL fetchCompletionHandlerCalled = NO; [delegate application:_mockApplication @@ -384,6 +534,21 @@ NS_ASSUME_NONNULL_BEGIN XCTAssertTrue(fetchCompletionHandlerCalled); XCTAssertNil(delegate.notificationReceived); + // Verify one of the `application:openURL:...` methods is handled. + OCMExpect([mockHandler canHandleURL:_url]).andReturn(YES); + if (&UIApplicationOpenURLOptionsAnnotationKey) { // iOS 9+ + // Verify `application:openURL:options:` is handled. + XCTAssertTrue([delegate application:_mockApplication openURL:_url options:@{}]); + } else { + // Verify `application:openURL:sourceApplication:annotation:` is handled. + XCTAssertTrue([delegate application:_mockApplication + openURL:_url + sourceApplication:@"sourceApplication" + annotation:@"annotaton"]); + } + OCMVerifyAll(mockHandler); + XCTAssertNil(delegate.urlOpened); + // Verify unaffected delegate instance. [unaffectedDelegate application:_mockApplication didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken]; @@ -399,6 +564,9 @@ NS_ASSUME_NONNULL_BEGIN XCTAssertTrue(fetchCompletionHandlerCalled); XCTAssertEqualObjects(unaffectedDelegate.notificationReceived, _notification); unaffectedDelegate.notificationReceived = nil; + XCTAssertFalse([unaffectedDelegate application:_mockApplication openURL:_url options:@{}]); + XCTAssertEqualObjects(unaffectedDelegate.urlOpened, _url); + unaffectedDelegate.urlOpened = nil; weakHandler = mockHandler; XCTAssertNotNil(weakHandler); @@ -421,6 +589,9 @@ NS_ASSUME_NONNULL_BEGIN XCTAssertEqualObjects(delegate.notificationReceived, _notification); delegate.notificationReceived = nil; XCTAssertTrue(fetchCompletionHandlerCalled); + XCTAssertFalse([delegate application:_mockApplication openURL:_url options:@{}]); + XCTAssertEqualObjects(delegate.urlOpened, _url); + delegate.urlOpened = nil; weakProxy = proxy; XCTAssertNotNil(weakProxy); @@ -443,6 +614,61 @@ NS_ASSUME_NONNULL_BEGIN XCTAssertEqualObjects(delegate.notificationReceived, _notification); delegate.notificationReceived = nil; XCTAssertTrue(fetchCompletionHandlerCalled); + XCTAssertFalse([delegate application:_mockApplication openURL:_url options:@{}]); + XCTAssertEqualObjects(delegate.urlOpened, _url); + delegate.urlOpened = nil; +} + +/** @fn testOtherLegacyDelegateHandleOpenURL + @brief Tests that the proxy works against another legacy @c UIApplicationDelegate for + `application:handleOpenURL:`. + */ +- (void)testOtherLegacyDelegateHandleOpenURL { + FIRAuthOtherLegacyAppDelegate *delegate = [[FIRAuthOtherLegacyAppDelegate alloc] init]; + OCMExpect([_mockApplication delegate]).andReturn(delegate); + __weak id weakProxy; + @autoreleasepool { + FIRAuthAppDelegateProxy *proxy = + [[FIRAuthAppDelegateProxy alloc] initWithApplication:_mockApplication]; + XCTAssertNotNil(proxy); + + // Verify certain methods are swizzled while others are not. + XCTAssertFalse([delegate respondsToSelector:@selector(application:openURL:options:)]); + XCTAssertFalse([delegate respondsToSelector: + @selector(application:openURL:sourceApplication:annotation:)]); + XCTAssertTrue([delegate respondsToSelector:@selector(application:handleOpenURL:)]); + + // Verify the handler is called after being added. + __weak id weakHandler; + @autoreleasepool { + id mockHandler = OCMProtocolMock(@protocol(FIRAuthAppDelegateHandler)); + [proxy addHandler:mockHandler]; + + // Verify `application:handleOpenURL:` is handled. + OCMExpect([mockHandler canHandleURL:_url]).andReturn(YES); + XCTAssertTrue([delegate application:_mockApplication handleOpenURL:_url]); + OCMVerifyAll(mockHandler); + + weakHandler = mockHandler; + XCTAssertNotNil(weakHandler); + } + // Verify the handler is not retained by the proxy. + XCTAssertNil(weakHandler); + + // Verify nothing bad happens after the handler is released. + XCTAssertFalse([delegate application:_mockApplication handleOpenURL:_url]); + XCTAssertEqualObjects(delegate.urlOpened, _url); + delegate.urlOpened = nil; + + weakProxy = proxy; + XCTAssertNotNil(weakProxy); + } + // Verify the proxy does not retain itself. + XCTAssertNil(weakProxy); + // Verify nothing bad happens after the proxy is released. + XCTAssertFalse([delegate application:_mockApplication handleOpenURL:_url]); + XCTAssertEqualObjects(delegate.urlOpened, _url); + delegate.urlOpened = nil; } @end diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m index 33af19e..0a8af0e 100644 --- a/Firebase/Auth/Source/FIRAuth.m +++ b/Firebase/Auth/Source/FIRAuth.m @@ -980,6 +980,10 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; } #endif +- (BOOL)canHandleURL:(NSURL *)url { + return NO; +} + #pragma mark - Internal Methods #if TARGET_OS_IOS diff --git a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h index 656e4f2..d974e2c 100644 --- a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h +++ b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.h @@ -37,6 +37,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)canHandleNotification:(nonnull NSDictionary *)notification; +/** @fn canHandleURL: + @brief Checks whether the URL can be handled by the receiver, and handles it if so. + @param url The URL in question, which will be consumed if returns @c YES. + @return Whether the URL can be (and already has been) handled by the receiver. + */ +- (BOOL)canHandleURL:(nonnull NSURL *)url; + @end /** @class FIRAuthAppDelegateProxy diff --git a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m index f24765a..15151f3 100644 --- a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m +++ b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m @@ -107,6 +107,53 @@ static id noop(id object, SEL cmd, ...) { didReceiveRemoteNotification:notification]; }]; } + SEL openURLOptionsSelector = @selector(application:openURL:options:); + SEL openURLAnnotationSelector = @selector(application:openURL:sourceApplication:annotation:); + SEL handleOpenURLSelector = @selector(application:handleOpenURL:); + if (&UIApplicationOpenURLOptionsAnnotationKey && // the constant is only available on iOS 9+ + ([_appDelegate respondsToSelector:openURLOptionsSelector] || + (![_appDelegate respondsToSelector:openURLAnnotationSelector] && + ![_appDelegate respondsToSelector:handleOpenURLSelector]))) { + // Replace the modern selector which is avaliable on iOS 9 and above because this is the one + // that the client app uses or the client app doesn't use any of them. + [self replaceSelector:openURLOptionsSelector + withBlock:^BOOL(id object, UIApplication *application, NSURL *url, + NSDictionary *options) { + return [weakSelf object:object + selector:openURLOptionsSelector + application:application + openURL:url + options:options]; + }]; + } else if ([_appDelegate respondsToSelector:openURLAnnotationSelector] || + ![_appDelegate respondsToSelector:handleOpenURLSelector]) { + // Replace the longer form of the deprecated selectors on iOS 8 and below because this is the + // one that the client app uses or the client app doesn't use either of the applicable ones. + [self replaceSelector:openURLAnnotationSelector + withBlock:^(id object, UIApplication *application, NSURL *url, + NSString *sourceApplication, id annotation) { + return [weakSelf object:object + selector:openURLAnnotationSelector + application:application + openURL:url + sourceApplication:sourceApplication + annotation:annotation]; + }]; + } else { + // Replace the shorter form of the deprecated selectors on iOS 8 and below because this is + // the only one that the client app uses. + [self replaceSelector:handleOpenURLSelector + withBlock:^(id object, UIApplication *application, NSURL *url) { + return [weakSelf object:object + selector:handleOpenURLSelector + application:application + handleOpenURL:url]; + }]; + } + // Reset the application delegate to clear the system cache that indicates whether each of the + // openURL: methods is implemented on the application delegate. + application.delegate = nil; + application.delegate = _appDelegate; } return self; } @@ -146,7 +193,7 @@ static id noop(id object, SEL cmd, ...) { } } IMP originalImplementation = [self originalImplementationForSelector:selector]; - if (originalImplementation) { + if (originalImplementation && originalImplementation != &noop) { typedef void (*Implmentation)(id, SEL, UIApplication*, NSData *); ((Implmentation)originalImplementation)(object, selector, application, deviceToken); } @@ -166,10 +213,12 @@ static id noop(id object, SEL cmd, ...) { } } IMP originalImplementation = [self originalImplementationForSelector:selector]; - typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *, - void (^)(UIBackgroundFetchResult)); - ((Implmentation)originalImplementation)(object, selector, application, notification, - completionHandler); + if (originalImplementation && originalImplementation != &noop) { + typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *, + void (^)(UIBackgroundFetchResult)); + ((Implmentation)originalImplementation)(object, selector, application, notification, + completionHandler); + } } - (void)object:(id)object @@ -184,12 +233,77 @@ static id noop(id object, SEL cmd, ...) { } } IMP originalImplementation = [self originalImplementationForSelector:selector]; - typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *); - ((Implmentation)originalImplementation)(object, selector, application, notification); + if (originalImplementation && originalImplementation != &noop) { + typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *); + ((Implmentation)originalImplementation)(object, selector, application, notification); + } +} + +- (BOOL)object:(id)object + selector:(SEL)selector + application:(UIApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options { + if (object == _appDelegate && [self delegateCanHandleURL:url]) { + return YES; + } + IMP originalImplementation = [self originalImplementationForSelector:selector]; + if (originalImplementation && originalImplementation != &noop) { + typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *, NSDictionary *); + return ((Implmentation)originalImplementation)(object, selector, application, url, options); + } + return NO; +} + +- (BOOL)object:(id)object + selector:(SEL)selector + application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication + annotation:(id)annotation { + if (object == _appDelegate && [self delegateCanHandleURL:url]) { + return YES; + } + IMP originalImplementation = [self originalImplementationForSelector:selector]; + if (originalImplementation && originalImplementation != &noop) { + typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *, NSString *, id); + return ((Implmentation)originalImplementation)(object, selector, application, url, + sourceApplication, annotation); + } + return NO; +} + +- (BOOL)object:(id)object + selector:(SEL)selector + application:(UIApplication *)application + handleOpenURL:(NSURL *)url { + if (object == _appDelegate && [self delegateCanHandleURL:url]) { + return YES; + } + IMP originalImplementation = [self originalImplementationForSelector:selector]; + if (originalImplementation && originalImplementation != &noop) { + typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *); + return ((Implmentation)originalImplementation)(object, selector, application, url); + } + return NO; } #pragma mark - Internal Methods +/** @fn delegateCanHandleURL: + @brief Checks for whether any of the delegates can handle the URL. + @param url The URL in question. + @return Whether any of the delegate can handle the URL. + */ +- (BOOL)delegateCanHandleURL:(NSURL *)url { + for (id<FIRAuthAppDelegateHandler> handler in [self handlers]) { + if ([handler canHandleURL:url]) { + return YES; + }; + } + return NO; +} + /** @fn handlers @brief Gets the list of handlers from `_handlers` safely. */ diff --git a/Firebase/Auth/Source/Public/FIRAuth.h b/Firebase/Auth/Source/Public/FIRAuth.h index ad6b635..f2b00ae 100644 --- a/Firebase/Auth/Source/Public/FIRAuth.h +++ b/Firebase/Auth/Source/Public/FIRAuth.h @@ -684,6 +684,18 @@ FIR_SWIFT_NAME(Auth) - (BOOL)canHandleNotification:(NSDictionary *)userInfo; #endif +/** @fn canHandleURL: + @brief Whether the specific URL is handled by @c FIRAuth . + @param url The URL received by the application delegate from any of the openURL method. + @return Whether or the URL is handled. YES means the URL is for Firebase Auth + so the caller should ignore the URL from further processing, and NO means the + the URL is for the app (or another libaray) so the caller should continue handling + this URL as usual. + @remarks If swizzling is disabled, URLs received by the application delegate must be forwarded + to this method for phone number auth to work. + */ +- (BOOL)canHandleURL:(nonnull NSURL *)url; + @end NS_ASSUME_NONNULL_END |