aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Xiangtian Dai <xiangtian@google.com>2017-08-21 21:14:20 -0700
committerGravatar GitHub <noreply@github.com>2017-08-21 21:14:20 -0700
commit6e12c501f8dde3057d26149826e82489e114b2a1 (patch)
tree21145365c0433793ed1f59240db8815812d24fce
parent2c19745fb8f263730a390bb959b00b4209e00916 (diff)
Forwards app delegate's openURL invocations to `FIRAuth`. (#207)
-rw-r--r--Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m256
-rw-r--r--Firebase/Auth/Source/FIRAuth.m4
-rw-r--r--Firebase/Auth/Source/FIRAuthAppDelegateProxy.h7
-rw-r--r--Firebase/Auth/Source/FIRAuthAppDelegateProxy.m128
-rw-r--r--Firebase/Auth/Source/Public/FIRAuth.h12
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