aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase
diff options
context:
space:
mode:
authorGravatar Xiangtian Dai <xiangtian@google.com>2017-09-12 13:49:05 -0700
committerGravatar GitHub <noreply@github.com>2017-09-12 13:49:05 -0700
commitc8ea66eecfb256925e457d14b64bc08984da4387 (patch)
treea5c01d7f36c447111092ec80cbde68cfdb7fca07 /Firebase
parente8cf906d21501513f53666a34d4c4eb3e95bef6e (diff)
Implements web view for presenting Auth web content on iOS 7 and 8. (#253)
Also (hopefully) fixes thread safety issues in presenting Auth web content.
Diffstat (limited to 'Firebase')
-rw-r--r--Firebase/Auth/FirebaseAuth.podspec2
-rw-r--r--Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m9
-rw-r--r--Firebase/Auth/Source/FIRAuthDefaultUIDelegate.h38
-rw-r--r--Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m14
-rw-r--r--Firebase/Auth/Source/FIRAuthURLPresenter.m99
-rw-r--r--Firebase/Auth/Source/FIRAuthWebView.h35
-rw-r--r--Firebase/Auth/Source/FIRAuthWebView.m86
-rw-r--r--Firebase/Auth/Source/FIRAuthWebViewController.h67
-rw-r--r--Firebase/Auth/Source/FIRAuthWebViewController.m111
9 files changed, 419 insertions, 42 deletions
diff --git a/Firebase/Auth/FirebaseAuth.podspec b/Firebase/Auth/FirebaseAuth.podspec
index ad4c54f..57b8a73 100644
--- a/Firebase/Auth/FirebaseAuth.podspec
+++ b/Firebase/Auth/FirebaseAuth.podspec
@@ -33,6 +33,8 @@ Simplify your iOS development, grow your user base, and monetize more effectivel
'Source/**/FIRAuthDefaultUIDelegate.[mh]',
'Source/**/FIRAuthUIDelegate.h',
'Source/**/FIRAuthURLPresenter.[mh]',
+ 'Source/**/FIRAuthWebView.[mh]',
+ 'Source/**/FIRAuthWebViewController.[mh]',
'Source/**/FIRPhoneAuthCredential.[mh]',
'Source/**/FIRPhoneAuthProvider.[mh]'
s.public_header_files = 'Source/Public/*.h'
diff --git a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m b/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m
index 32b5da0..7587f1b 100644
--- a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m
+++ b/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m
@@ -146,15 +146,16 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@";
callBackOnMainThread(nil, error);
return;
}
+ FIRAuthURLCallbackMatcher callbackMatcher = ^BOOL(NSURL *_Nullable callbackURL) {
+ return [self isVerifyAppURL:callbackURL];
+ };
[_auth.authURLPresenter presentURL:reCAPTCHAURL
UIDelegate:UIDelegate
- callbackMatcher:^BOOL(NSURL * _Nullable callbackURL) {
- return [self isVerifyAppURL:callbackURL];
- }
+ callbackMatcher:callbackMatcher
completion:^(NSURL *_Nullable callbackURL,
NSError *_Nullable error) {
if (error) {
- completion(nil, error);
+ callBackOnMainThread(nil, error);
return;
}
NSError *reCAPTCHAError;
diff --git a/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.h b/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.h
new file mode 100644
index 0000000..f0e5d80
--- /dev/null
+++ b/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "FIRAuthUIDelegate.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRAuthDefaultUIDelegate : NSObject <FIRAuthUIDelegate>
+
+/** @fn defaultUIDelegate
+ @brief Unavailable. Please use @c +defaultUIDelegate:
+ */
+- (instancetype)init NS_UNAVAILABLE;
+
+/** @fn defaultUIDelegate
+ @brief Returns a default FIRAuthUIDelegate object.
+ @return The default FIRAuthUIDelegate object.
+ */
++ (id<FIRAuthUIDelegate>)defaultUIDelegate;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m b/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m
index 118b73c..a00d0e9 100644
--- a/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m
+++ b/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m
@@ -14,24 +14,20 @@
* limitations under the License.
*/
-#import <Foundation/Foundation.h>
-
-#import "FIRAuthUIDelegate.h"
+#import "FIRAuthDefaultUIDelegate.h"
NS_ASSUME_NONNULL_BEGIN
-@interface FIRAuthDefaultUIDelegate : NSObject <FIRAuthUIDelegate>
-/** @fn defaultUIDelegate
- @brief Unavailable. Please use initWithViewController:
- */
-- (instancetype)init NS_UNAVAILABLE;
+@interface FIRAuthDefaultUIDelegate ()
/** @fn initWithViewController:
@brief Initializes the instance with a view controller.
- @param viewController The view controller as the presenting view controller in @c GOIUIDelegate.
+ @param viewController The view controller as the presenting view controller in @c
+ FIRAuthUIDelegate.
@return The initialized instance.
*/
- (instancetype)initWithViewController:(UIViewController *)viewController NS_DESIGNATED_INITIALIZER;
+
@end
@implementation FIRAuthDefaultUIDelegate {
diff --git a/Firebase/Auth/Source/FIRAuthURLPresenter.m b/Firebase/Auth/Source/FIRAuthURLPresenter.m
index 722326c..d923c8a 100644
--- a/Firebase/Auth/Source/FIRAuthURLPresenter.m
+++ b/Firebase/Auth/Source/FIRAuthURLPresenter.m
@@ -18,20 +18,15 @@
#import <SafariServices/SafariServices.h>
+#import "FIRAuthDefaultUIDelegate.h"
#import "FIRAuthErrorUtils.h"
+#import "FIRAuthGlobalWorkQueue.h"
#import "FIRAuthUIDelegate.h"
+#import "FIRAuthWebViewController.h"
NS_ASSUME_NONNULL_BEGIN
-@interface FIRAuthDefaultUIDelegate : NSObject <FIRAuthUIDelegate>
-/** @fn defaultUIDelegate
- @brief Returns a default FIRAuthUIDelegate object.
- @return The default FIRAuthUIDelegate object.
- */
-+ (id<FIRAuthUIDelegate>)defaultUIDelegate;
-@end
-
-@interface FIRAuthURLPresenter () <SFSafariViewControllerDelegate>
+@interface FIRAuthURLPresenter () <SFSafariViewControllerDelegate, FIRAuthWebViewDelegate>
@end
@implementation FIRAuthURLPresenter {
@@ -50,6 +45,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
SFSafariViewController *_Nullable _safariViewController;
+ /** @var _webViewController
+ @brief The FIRAuthWebViewController used for the current presentation, if any.
+ */
+ FIRAuthWebViewController *_Nullable _webViewController;
+
/** @var _UIDelegate
@brief The UIDelegate used to present the SFSafariViewController.
*/
@@ -74,17 +74,20 @@ NS_ASSUME_NONNULL_BEGIN
_isPresenting = YES;
_callbackMatcher = callbackMatcher;
_completion = completion;
- _UIDelegate = UIDelegate ?: [FIRAuthDefaultUIDelegate defaultUIDelegate];
- if ([SFSafariViewController class]) {
- SFSafariViewController *safariViewController = [[SFSafariViewController alloc] initWithURL:URL];
- _safariViewController = safariViewController;
- _safariViewController.delegate = self;
- [_UIDelegate presentViewController:safariViewController animated:YES completion:nil];
- return;
- } else {
- // TODO: Use web view instead.
- [[UIApplication sharedApplication] openURL:URL];
- }
+ dispatch_async(dispatch_get_main_queue(), ^() {
+ _UIDelegate = UIDelegate ?: [FIRAuthDefaultUIDelegate defaultUIDelegate];
+ if ([SFSafariViewController class]) {
+ _safariViewController = [[SFSafariViewController alloc] initWithURL:URL];
+ _safariViewController.delegate = self;
+ [_UIDelegate presentViewController:_safariViewController animated:YES completion:nil];
+ return;
+ } else {
+ _webViewController = [[FIRAuthWebViewController alloc] initWithURL:URL delegate:self];
+ UINavigationController *navController =
+ [[UINavigationController alloc] initWithRootViewController:_webViewController];
+ [_UIDelegate presentViewController:navController animated:YES completion:nil];
+ }
+ });
}
- (BOOL)canHandleURL:(NSURL *)URL {
@@ -98,13 +101,45 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - SFSafariViewControllerDelegate
- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller {
- if (controller == _safariViewController) {
- _safariViewController = nil;
- //TODO:Ensure that the SFSafariViewController is actually removed from the screen before
- //invoking finishPresentationWithURL:error:
- [self finishPresentationWithURL:nil
- error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]];
- }
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ if (controller == _safariViewController) {
+ _safariViewController = nil;
+ //TODO:Ensure that the SFSafariViewController is actually removed from the screen before
+ //invoking finishPresentationWithURL:error:
+ [self finishPresentationWithURL:nil
+ error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]];
+ }
+ });
+}
+
+#pragma mark - FIRAuthwebViewControllerDelegate
+
+- (BOOL)webViewController:(FIRAuthWebViewController *)webViewController canHandleURL:(NSURL *)URL {
+ __block BOOL result = NO;
+ dispatch_sync(FIRAuthGlobalWorkQueue(), ^() {
+ if (webViewController == _webViewController) {
+ result = [self canHandleURL:URL];
+ }
+ });
+ return result;
+}
+
+- (void)webViewControllerDidCancel:(FIRAuthWebViewController *)webViewController {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ if (webViewController == _webViewController) {
+ [self finishPresentationWithURL:nil
+ error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]];
+ }
+ });
+}
+
+- (void)webViewController:(FIRAuthWebViewController *)webViewController
+ didFailWithError:(NSError *)error {
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ if (webViewController == _webViewController) {
+ [self finishPresentationWithURL:nil error:error];
+ }
+ });
}
#pragma mark - Private methods
@@ -127,8 +162,14 @@ NS_ASSUME_NONNULL_BEGIN
};
SFSafariViewController *safariViewController = _safariViewController;
_safariViewController = nil;
- if (safariViewController) {
- [UIDelegate dismissViewControllerAnimated:YES completion:finishBlock];
+ FIRAuthWebViewController *webViewController = _webViewController;
+ _webViewController = nil;
+ if (safariViewController || webViewController) {
+ dispatch_async(dispatch_get_main_queue(), ^() {
+ [UIDelegate dismissViewControllerAnimated:YES completion:^() {
+ dispatch_async(FIRAuthGlobalWorkQueue(), finishBlock);
+ }];
+ });
} else {
finishBlock();
}
diff --git a/Firebase/Auth/Source/FIRAuthWebView.h b/Firebase/Auth/Source/FIRAuthWebView.h
new file mode 100644
index 0000000..f4a8d1b
--- /dev/null
+++ b/Firebase/Auth/Source/FIRAuthWebView.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRAuthWebView : UIView
+
+/** @property webView
+ * @brief The web view.
+ */
+@property(nonatomic, weak) UIWebView *webView;
+
+/** @property spinner
+ * @brief The spinner that indicates web view loading.
+ */
+@property(nonatomic, weak) UIActivityIndicatorView *spinner;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/FIRAuthWebView.m b/Firebase/Auth/Source/FIRAuthWebView.m
new file mode 100644
index 0000000..80b90f0
--- /dev/null
+++ b/Firebase/Auth/Source/FIRAuthWebView.m
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRAuthWebView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation FIRAuthWebView
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+ if (self) {
+ self.backgroundColor = [UIColor whiteColor];
+ [self initializeSubviews];
+ }
+ return self;
+}
+
+/** @fn initializeSubviews
+ @brief Initializes the subviews of this view.
+ */
+- (void)initializeSubviews {
+ UIWebView *webView = [self createWebView];
+ UIActivityIndicatorView *spinner = [self createSpinner];
+
+ // The order of the following controls z-order.
+ [self addSubview:webView];
+ [self addSubview:spinner];
+
+ [self layoutSubviews];
+ _webView = webView;
+ _spinner = spinner;
+}
+
+- (void)layoutSubviews {
+ CGFloat height = self.bounds.size.height;
+ CGFloat width = self.bounds.size.width;
+ _webView.frame = CGRectMake(0, 0, width, height);
+ _spinner.center = _webView.center;
+}
+
+/** @fn createWebView
+ @brief Creates a web view to be used by this view.
+ @return The newly created web view.
+ */
+- (UIWebView *)createWebView {
+ UIWebView *webView = [[UIWebView alloc] initWithFrame:CGRectZero];
+ // Trickery to make the web view not do weird things (like showing a black background when
+ // the prompt in the navigation bar animates changes.)
+ webView.opaque = NO;
+ webView.backgroundColor = [UIColor clearColor];
+ webView.scrollView.opaque = NO;
+ webView.scrollView.backgroundColor = [UIColor clearColor];
+ webView.scrollView.bounces = NO;
+ webView.scrollView.alwaysBounceVertical = NO;
+ webView.scrollView.alwaysBounceHorizontal = NO;
+ return webView;
+}
+
+/** @fn createSpinner
+ @brief Creates a spinner to be used by this view.
+ @return The newly created spinner.
+ */
+- (UIActivityIndicatorView *)createSpinner {
+ UIActivityIndicatorViewStyle spinnerStyle = UIActivityIndicatorViewStyleGray;
+ UIActivityIndicatorView *spinner =
+ [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:spinnerStyle];
+ return spinner;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/FIRAuthWebViewController.h b/Firebase/Auth/Source/FIRAuthWebViewController.h
new file mode 100644
index 0000000..5c2c042
--- /dev/null
+++ b/Firebase/Auth/Source/FIRAuthWebViewController.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <UIKit/UIKit.h>
+
+@class FIRAuthWebViewController;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol FIRAuthWebViewDelegate <NSObject>
+
+/** @fn webViewController:canHandleURL:
+ @brief Determines if a URL should be handled by the delegate.
+ @param URL The URL to handle.
+ @return Whether the URL could be handled or not.
+ */
+- (BOOL)webViewController:(FIRAuthWebViewController *)webViewController canHandleURL:(NSURL *)URL;
+
+/** @fn webViewControllerDidCancel:
+ @brief Notifies the delegate that the web view controller is being cancelled by the user.
+ @param webViewController The web view controller in question.
+ */
+- (void)webViewControllerDidCancel:(FIRAuthWebViewController *)webViewController;
+
+/** @fn webViewController:didFailWithError:
+ @brief Notifies the delegate that the web view controller failed to load a page.
+ @param webViewController The web view controller in question.
+ @param error The error that has occurred.
+ */
+- (void)webViewController:(FIRAuthWebViewController *)webViewController
+ didFailWithError:(NSError *)error;
+
+@end
+
+@interface FIRAuthWebViewController : UIViewController
+
+/** @fn initWithNibName:bundle:
+ * @brief Please call initWithURL:delegate:
+ */
+- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil
+ bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE;
+
+/** @fn initWithCoder:
+ * @brief Please call initWithURL:delegate:
+ */
+- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
+
+- (instancetype)initWithURL:(NSURL *)URL
+ delegate:(__weak id<FIRAuthWebViewDelegate>)delegate
+ NS_DESIGNATED_INITIALIZER;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Auth/Source/FIRAuthWebViewController.m b/Firebase/Auth/Source/FIRAuthWebViewController.m
new file mode 100644
index 0000000..b9a2473
--- /dev/null
+++ b/Firebase/Auth/Source/FIRAuthWebViewController.m
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRAuthWebViewController.h"
+
+#import "FIRAuthWebView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRAuthWebViewController () <UIWebViewDelegate>
+@end
+
+@implementation FIRAuthWebViewController {
+ /** @var _URL
+ @brief The initial URL to display.
+ */
+ NSURL *_URL;
+
+ /** @var _delegate
+ @brief The delegate to call.
+ */
+ __weak id<FIRAuthWebViewDelegate> _delegate;
+
+ /** @var _webView;
+ @brief The web view instance for easier access.
+ */
+ __weak FIRAuthWebView *_webView;
+}
+
+- (instancetype)initWithURL:(NSURL *)URL
+ delegate:(__weak id<FIRAuthWebViewDelegate>)delegate {
+ self = [super initWithNibName:nil bundle:nil];
+ if (self) {
+ _URL = URL;
+ _delegate = delegate;
+ }
+ return self;
+}
+
+#pragma mark - Lifecycle
+
+- (void)loadView {
+ FIRAuthWebView *webView = [[FIRAuthWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
+ webView.webView.delegate = self;
+ self.view = webView;
+ _webView = webView;
+ self.navigationItem.leftBarButtonItem =
+ [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
+ target:self
+ action:@selector(cancel)];
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+
+ // Loads the requested URL in the web view.
+ [_webView.webView loadRequest:[NSURLRequest requestWithURL:_URL]];
+}
+
+#pragma mark - UI Targets
+
+- (void)cancel {
+ [_delegate webViewControllerDidCancel:self];
+}
+
+#pragma mark - UIWebViewDelegate
+
+- (BOOL)webView:(UIWebView *)webView
+ shouldStartLoadWithRequest:(NSURLRequest *)request
+ navigationType:(UIWebViewNavigationType)navigationType {
+ return ![_delegate webViewController:self canHandleURL:request.URL];
+}
+
+- (void)webViewDidStartLoad:(UIWebView *)webView {
+ // Show & animate the activity indicator.
+ _webView.spinner.hidden = NO;
+ [_webView.spinner startAnimating];
+}
+
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ // Hide & stop the activity indicator.
+ _webView.spinner.hidden = YES;
+ [_webView.spinner stopAnimating];
+}
+
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
+ // It's okay for the page to be redirected before it is completely loaded. See b/32028062 .
+ return;
+ }
+ // Forward notification to our delegate.
+ [self webViewDidFinishLoad:webView];
+ [_delegate webViewController:self didFailWithError:error];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END