- Add Facebook SDK

- Add PNUser Login
This commit is contained in:
giuseppenuc committed 2016-02-12 00:01:51 +01:00
1 parent f2885301b9
commit a6dcf672bb
370 files changed
+44655 -2705

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
/*! The version of the App Link protocol that this library supports */
FOUNDATION_EXPORT NSString *const BFAppLinkVersion;
/*!
Contains App Link metadata relevant for navigation on this device
derived from the HTML at a given URL.
*/
@interface BFAppLink : NSObject
/*!
Creates a BFAppLink with the given list of BFAppLinkTargets and target URL.
Generally, this will only be used by implementers of the BFAppLinkResolving protocol,
as these implementers will produce App Link metadata for a given URL.
@param sourceURL the URL from which this App Link is derived
@param targets an ordered list of BFAppLinkTargets for this platform derived
from App Link metadata.
@param webURL the fallback web URL, if any, for the app link.
*/
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
targets:(NSArray *)targets
webURL:(NSURL *)webURL;
/*! The URL from which this BFAppLink was derived */
@property (nonatomic, strong, readonly) NSURL *sourceURL;
/*!
The ordered list of targets applicable to this platform that will be used
for navigation.
*/
@property (nonatomic, copy, readonly) NSArray *targets;
/*! The fallback web URL to use if no targets are installed on this device. */
@property (nonatomic, strong, readonly) NSURL *webURL;
@end
+62
View File
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFAppLink_Internal.h"
NSString *const BFAppLinkDataParameterName = @"al_applink_data";
NSString *const BFAppLinkTargetKeyName = @"target_url";
NSString *const BFAppLinkUserAgentKeyName = @"user_agent";
NSString *const BFAppLinkExtrasKeyName = @"extras";
NSString *const BFAppLinkRefererAppLink = @"referer_app_link";
NSString *const BFAppLinkRefererAppName = @"app_name";
NSString *const BFAppLinkRefererUrl = @"url";
NSString *const BFAppLinkVersionKeyName = @"version";
NSString *const BFAppLinkVersion = @"1.0";
@interface BFAppLink ()
@property (nonatomic, strong, readwrite) NSURL *sourceURL;
@property (nonatomic, copy, readwrite) NSArray *targets;
@property (nonatomic, strong, readwrite) NSURL *webURL;
@property (nonatomic, assign, readwrite, getter=isBackToReferrer) BOOL backToReferrer;
@end
@implementation BFAppLink
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
targets:(NSArray *)targets
webURL:(NSURL *)webURL
isBackToReferrer:(BOOL)isBackToReferrer {
BFAppLink *link = [[self alloc] initWithIsBackToReferrer:isBackToReferrer];
link.sourceURL = sourceURL;
link.targets = [targets copy];
link.webURL = webURL;
return link;
}
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
targets:(NSArray *)targets
webURL:(NSURL *)webURL {
return [self appLinkWithSourceURL:sourceURL
targets:targets
webURL:webURL
isBackToReferrer:NO];
}
- (BFAppLink *)initWithIsBackToReferrer:(BOOL)backToReferrer {
if ((self = [super init])) {
_backToReferrer = backToReferrer;
}
return self;
}
@end
+93
View File
@@ -0,0 +1,93 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <Bolts/BFAppLink.h>
/*!
The result of calling navigate on a BFAppLinkNavigation
*/
typedef NS_ENUM(NSInteger, BFAppLinkNavigationType) {
/*! Indicates that the navigation failed and no app was opened */
BFAppLinkNavigationTypeFailure,
/*! Indicates that the navigation succeeded by opening the URL in the browser */
BFAppLinkNavigationTypeBrowser,
/*! Indicates that the navigation succeeded by opening the URL in an app on the device */
BFAppLinkNavigationTypeApp
};
@protocol BFAppLinkResolving;
@class BFTask;
/*!
Represents a pending request to navigate to an App Link. Most developers will
simply use navigateToURLInBackground: to open a URL, but developers can build
custom requests with additional navigation and app data attached to them by
creating BFAppLinkNavigations themselves.
*/
@interface BFAppLinkNavigation : NSObject
/*!
The extras for the AppLinkNavigation. This will generally contain application-specific
data that should be passed along with the request, such as advertiser or affiliate IDs or
other such metadata relevant on this device.
*/
@property (nonatomic, copy, readonly) NSDictionary *extras;
/*!
The al_applink_data for the AppLinkNavigation. This will generally contain data common to
navigation attempts such as back-links, user agents, and other information that may be used
in routing and handling an App Link request.
*/
@property (nonatomic, copy, readonly) NSDictionary *appLinkData;
/*! The AppLink to navigate to */
@property (nonatomic, strong, readonly) BFAppLink *appLink;
/*! Creates an AppLinkNavigation with the given link, extras, and App Link data */
+ (instancetype)navigationWithAppLink:(BFAppLink *)appLink
extras:(NSDictionary *)extras
appLinkData:(NSDictionary *)appLinkData;
/*! Performs the navigation */
- (BFAppLinkNavigationType)navigate:(NSError **)error;
/*! Returns a BFAppLink for the given URL */
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination;
/*! Returns a BFAppLink for the given URL using the given App Link resolution strategy */
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination resolver:(id<BFAppLinkResolving>)resolver;
/*! Navigates to a BFAppLink and returns whether it opened in-app or in-browser */
+ (BFAppLinkNavigationType)navigateToAppLink:(BFAppLink *)link error:(NSError **)error;
/*! Navigates to a URL (an asynchronous action) and returns a BFNavigationType */
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination;
/*!
Navigates to a URL (an asynchronous action) using the given App Link resolution
strategy and returns a BFNavigationType
*/
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination resolver:(id<BFAppLinkResolving>)resolver;
/*!
Gets the default resolver to be used for App Link resolution. If the developer has not set one explicitly,
a basic, built-in resolver will be used.
*/
+ (id<BFAppLinkResolving>)defaultResolver;
/*!
Sets the default resolver to be used for App Link resolution. Setting this to nil will revert the
default resolver to the basic, built-in resolver provided by Bolts.
*/
+ (void)setDefaultResolver:(id<BFAppLinkResolving>)resolver;
@end
+248
View File
@@ -0,0 +1,248 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
#import "BFAppLinkNavigation.h"
#import "BFTaskCompletionSource.h"
#import "BFAppLinkTarget.h"
#import "BoltsVersion.h"
#import "BFWebViewAppLinkResolver.h"
#import "BFExecutor.h"
#import "BFTask.h"
#import "BFMeasurementEvent_Internal.h"
#import "BFAppLink_Internal.h"
FOUNDATION_EXPORT NSString *const BFAppLinkDataParameterName;
FOUNDATION_EXPORT NSString *const BFAppLinkTargetKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkUserAgentKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkExtrasKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkVersionKeyName;
static id<BFAppLinkResolving> defaultResolver;
@interface BFAppLinkNavigation ()
@property (nonatomic, copy, readwrite) NSDictionary *extras;
@property (nonatomic, copy, readwrite) NSDictionary *appLinkData;
@property (nonatomic, strong, readwrite) BFAppLink *appLink;
@end
@implementation BFAppLinkNavigation
+ (instancetype)navigationWithAppLink:(BFAppLink *)appLink
extras:(NSDictionary *)extras
appLinkData:(NSDictionary *)appLinkData {
BFAppLinkNavigation *navigation = [[self alloc] init];
navigation.appLink = appLink;
navigation.extras = extras;
navigation.appLinkData = appLinkData;
return navigation;
}
- (NSString *)stringByEscapingQueryString:(NSString *)string {
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9
return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
#else
return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)string,
NULL,
(CFStringRef) @":/?#[]@!$&'()*+,;=",
kCFStringEncodingUTF8));
#endif
}
- (NSURL *)appLinkURLWithTargetURL:(NSURL *)targetUrl error:(NSError **)error {
NSMutableDictionary *appLinkData = [NSMutableDictionary dictionaryWithDictionary:self.appLinkData ?: @{}];
// Add applink protocol data
if (!appLinkData[BFAppLinkUserAgentKeyName]) {
appLinkData[BFAppLinkUserAgentKeyName] = [NSString stringWithFormat:@"Bolts iOS %@", BOLTS_VERSION];
}
if (!appLinkData[BFAppLinkVersionKeyName]) {
appLinkData[BFAppLinkVersionKeyName] = BFAppLinkVersion;
}
appLinkData[BFAppLinkTargetKeyName] = [self.appLink.sourceURL absoluteString];
appLinkData[BFAppLinkExtrasKeyName] = self.extras ?: @{};
// JSON-ify the applink data
NSError *jsonError = nil;
NSData *jsonBlob = [NSJSONSerialization dataWithJSONObject:appLinkData options:0 error:&jsonError];
if (!jsonError) {
NSString *jsonString = [[NSString alloc] initWithData:jsonBlob encoding:NSUTF8StringEncoding];
NSString *encoded = [self stringByEscapingQueryString:jsonString];
NSString *endUrlString = [NSString stringWithFormat:@"%@%@%@=%@",
[targetUrl absoluteString],
targetUrl.query ? @"&" : @"?",
BFAppLinkDataParameterName,
encoded];
return [NSURL URLWithString:endUrlString];
} else {
if (error) {
*error = jsonError;
}
// If there was an error encoding the app link data, fail hard.
return nil;
}
}
- (BFAppLinkNavigationType)navigate:(NSError **)error {
NSURL *openedURL = nil;
NSError *encodingError = nil;
BFAppLinkNavigationType retType = BFAppLinkNavigationTypeFailure;
// Find the first eligible/launchable target in the BFAppLink.
for (BFAppLinkTarget *target in self.appLink.targets) {
NSURL *appLinkAppURL = [self appLinkURLWithTargetURL:target.URL error:&encodingError];
if (encodingError || !appLinkAppURL) {
if (error) {
*error = encodingError;
}
} else if ([[UIApplication sharedApplication] openURL:appLinkAppURL]) {
retType = BFAppLinkNavigationTypeApp;
openedURL = appLinkAppURL;
break;
}
}
if (!openedURL && self.appLink.webURL) {
// Fall back to opening the url in the browser if available.
NSURL *appLinkBrowserURL = [self appLinkURLWithTargetURL:self.appLink.webURL error:&encodingError];
if (encodingError || !appLinkBrowserURL) {
// If there was an error encoding the app link data, fail hard.
if (error) {
*error = encodingError;
}
} else if ([[UIApplication sharedApplication] openURL:appLinkBrowserURL]) {
// This was a browser navigation.
retType = BFAppLinkNavigationTypeBrowser;
openedURL = appLinkBrowserURL;
}
}
[self postAppLinkNavigateEventNotificationWithTargetURL:openedURL
error:error ? *error : nil
type:retType];
return retType;
}
- (void)postAppLinkNavigateEventNotificationWithTargetURL:(NSURL *)outputURL error:(NSError *)error type:(BFAppLinkNavigationType)type {
NSString *const EVENT_YES_VAL = @"1";
NSString *const EVENT_NO_VAL = @"0";
NSMutableDictionary *logData = [[NSMutableDictionary alloc] init];
NSString *outputURLScheme = [outputURL scheme];
NSString *outputURLString = [outputURL absoluteString];
if (outputURLScheme) {
logData[@"outputURLScheme"] = outputURLScheme;
}
if (outputURLString) {
logData[@"outputURL"] = outputURLString;
}
NSString *sourceURLString = [self.appLink.sourceURL absoluteString];
NSString *sourceURLHost = [self.appLink.sourceURL host];
NSString *sourceURLScheme = [self.appLink.sourceURL scheme];
if (sourceURLString) {
logData[@"sourceURL"] = sourceURLString;
}
if (sourceURLHost) {
logData[@"sourceHost"] = sourceURLHost;
}
if (sourceURLScheme) {
logData[@"sourceScheme"] = sourceURLScheme;
}
if ([error localizedDescription]) {
logData[@"error"] = [error localizedDescription];
}
NSString *success = nil; //no
NSString *linkType = nil; // unknown;
switch (type) {
case BFAppLinkNavigationTypeFailure:
success = EVENT_NO_VAL;
linkType = @"fail";
break;
case BFAppLinkNavigationTypeBrowser:
success = EVENT_YES_VAL;
linkType = @"web";
break;
case BFAppLinkNavigationTypeApp:
success = EVENT_YES_VAL;
linkType = @"app";
break;
default:
break;
}
if (success) {
logData[@"success"] = success;
}
if (linkType) {
logData[@"type"] = linkType;
}
if ([self.appLink isBackToReferrer]) {
[BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateBackToReferrerEventName args:logData];
} else {
[BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateOutEventName args:logData];
}
}
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination resolver:(id<BFAppLinkResolving>)resolver {
return [resolver appLinkFromURLInBackground:destination];
}
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination {
return [self resolveAppLinkInBackground:destination resolver:[self defaultResolver]];
}
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination {
return [self navigateToURLInBackground:destination
resolver:[self defaultResolver]];
}
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination
resolver:(id<BFAppLinkResolving>)resolver {
BFTask *resolutionTask = [self resolveAppLinkInBackground:destination
resolver:resolver];
return [resolutionTask continueWithExecutor:[BFExecutor mainThreadExecutor]
withSuccessBlock:^id(BFTask *task) {
NSError *error = nil;
BFAppLinkNavigationType result = [self navigateToAppLink:task.result
error:&error];
if (error) {
return [BFTask taskWithError:error];
} else {
return @(result);
}
}];
}
+ (BFAppLinkNavigationType)navigateToAppLink:(BFAppLink *)link error:(NSError **)error {
return [[BFAppLinkNavigation navigationWithAppLink:link
extras:nil
appLinkData:nil] navigate:error];
}
+ (id<BFAppLinkResolving>)defaultResolver {
if (defaultResolver) {
return defaultResolver;
}
return [BFWebViewAppLinkResolver sharedInstance];
}
+ (void)setDefaultResolver:(id<BFAppLinkResolving>)resolver {
defaultResolver = resolver;
}
@end
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
@class BFTask;
/*!
Implement this protocol to provide an alternate strategy for resolving
App Links that may include pre-fetching, caching, or querying for App Link
data from an index provided by a service provider.
*/
@protocol BFAppLinkResolving <NSObject>
/*!
Asynchronously resolves App Link data for a given URL.
@param url The URL to resolve into an App Link.
@returns A BFTask that will return a BFAppLink for the given URL.
*/
- (BFTask *)appLinkFromURLInBackground:(NSURL *)url;
@end
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <Bolts/BFAppLinkReturnToRefererView.h>
@class BFAppLink;
@class BFAppLinkReturnToRefererController;
/*!
Protocol that a class can implement in order to be notified when the user has navigated back
to the referer of an App Link.
*/
@protocol BFAppLinkReturnToRefererControllerDelegate <NSObject>
@optional
/*! Called when the user has tapped to navigate, but before the navigation has been performed. */
- (void)returnToRefererController:(BFAppLinkReturnToRefererController *)controller
willNavigateToAppLink:(BFAppLink *)appLink;
/*! Called after the navigation has been attempted, with an indication of whether the referer
app link was successfully opened. */
- (void)returnToRefererController:(BFAppLinkReturnToRefererController *)controller
didNavigateToAppLink:(BFAppLink *)url
type:(BFAppLinkNavigationType)type;
@end
/*!
A controller class that implements default behavior for a BFAppLinkReturnToRefererView, including
the ability to display the view above the navigation bar for navigation-based apps.
*/
@interface BFAppLinkReturnToRefererController : NSObject <BFAppLinkReturnToRefererViewDelegate>
/*!
The delegate that will be notified when the user navigates back to the referer.
*/
@property (nonatomic, weak) id<BFAppLinkReturnToRefererControllerDelegate> delegate;
/*!
The BFAppLinkReturnToRefererView this controller is controlling.
*/
@property (nonatomic, strong) BFAppLinkReturnToRefererView *view;
/*!
Initializes a controller suitable for controlling a BFAppLinkReturnToRefererView that is to be displayed
contained within another UIView (i.e., not displayed above the navigation bar).
*/
- (instancetype)init;
/*!
Initializes a controller suitable for controlling a BFAppLinkReturnToRefererView that is to be displayed
displayed above the navigation bar.
*/
- (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController;
/*!
Removes the view entirely from the navigation controller it is currently displayed in.
*/
- (void)removeFromNavController;
/*!
Shows the BFAppLinkReturnToRefererView with the specified referer information. If nil or missing data,
the view will not be displayed. */
- (void)showViewForRefererAppLink:(BFAppLink *)refererAppLink;
/*!
Shows the BFAppLinkReturnToRefererView with referer information extracted from the specified URL.
If nil or missing referer App Link data, the view will not be displayed. */
- (void)showViewForRefererURL:(NSURL *)url;
/*!
Closes the view, possibly animating it.
*/
- (void)closeViewAnimated:(BOOL)animated;
@end
@@ -0,0 +1,230 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFAppLinkReturnToRefererController.h"
#import "BFAppLink.h"
#import "BFAppLinkReturnToRefererView_Internal.h"
#import "BFURL_Internal.h"
static const CFTimeInterval kBFViewAnimationDuration = 0.25f;
@implementation BFAppLinkReturnToRefererController {
UINavigationController *_navigationController;
BFAppLinkReturnToRefererView *_view;
}
#pragma mark - Object lifecycle
- (instancetype)init {
return [self initForDisplayAboveNavController:nil];
}
- (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController {
self = [super init];
if (self) {
_navigationController = navController;
if (_navigationController != nil) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:@selector(statusBarFrameWillChange:)
name:UIApplicationWillChangeStatusBarFrameNotification
object:nil];
[nc addObserver:self
selector:@selector(statusBarFrameDidChange:)
name:UIApplicationDidChangeStatusBarFrameNotification
object:nil];
[nc addObserver:self
selector:@selector(orientationDidChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
}
return self;
}
- (void)dealloc {
_view.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Public API
- (BFAppLinkReturnToRefererView *)view {
if (!_view) {
self.view = [[BFAppLinkReturnToRefererView alloc] initWithFrame:CGRectZero];
if (_navigationController) {
[_navigationController.view addSubview:_view];
}
}
return _view;
}
- (void)setView:(BFAppLinkReturnToRefererView *)view {
if (_view != view) {
_view.delegate = nil;
}
_view = view;
_view.delegate = self;
if (_navigationController) {
_view.includeStatusBarInSize = BFIncludeStatusBarInSizeAlways;
}
}
- (void)showViewForRefererAppLink:(BFAppLink *)refererAppLink {
self.view.refererAppLink = refererAppLink;
[_view sizeToFit];
if (_navigationController) {
if (!_view.closed) {
dispatch_async(dispatch_get_main_queue(), ^{
[self moveNavigationBar];
});
}
}
}
- (void)showViewForRefererURL:(NSURL *)url {
BFAppLink *appLink = [BFURL URLForRenderBackToReferrerBarURL:url].appLinkReferer;
[self showViewForRefererAppLink:appLink];
}
- (void)removeFromNavController {
if (_navigationController) {
[_view removeFromSuperview];
_navigationController = nil;
}
}
#pragma mark - BFAppLinkReturnToRefererViewDelegate
- (void)returnToRefererViewDidTapInsideCloseButton:(BFAppLinkReturnToRefererView *)view {
[self closeViewAnimated:YES explicitlyClosed:YES];
}
- (void)returnToRefererViewDidTapInsideLink:(BFAppLinkReturnToRefererView *)view
link:(BFAppLink *)link {
[self openRefererAppLink:link];
[self closeViewAnimated:NO explicitlyClosed:NO];
}
#pragma mark - Private
- (void)statusBarFrameWillChange:(NSNotification *)notification {
NSValue *rectValue = [[notification userInfo] valueForKey:UIApplicationStatusBarFrameUserInfoKey];
CGRect newFrame;
[rectValue getValue:&newFrame];
if (_navigationController && !_view.closed) {
if (CGRectGetHeight(newFrame) == 40) {
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:kBFViewAnimationDuration delay:0.0 options:options animations:^{
_view.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(_view.bounds), 0.0);
} completion:nil];
}
}
}
- (void)statusBarFrameDidChange:(NSNotification *)notification {
NSValue *rectValue = [[notification userInfo] valueForKey:UIApplicationStatusBarFrameUserInfoKey];
CGRect newFrame;
[rectValue getValue:&newFrame];
if (_navigationController && !_view.closed) {
if (CGRectGetHeight(newFrame) == 40) {
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:kBFViewAnimationDuration delay:0.0 options:options animations:^{
[_view sizeToFit];
[self moveNavigationBar];
} completion:nil];
}
}
}
- (void)orientationDidChange:(NSNotificationCenter *)notification {
if (_navigationController && !_view.closed && CGRectGetHeight(_view.bounds) > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self moveNavigationBar];
});
}
}
- (void)moveNavigationBar {
if (_view.closed || !_view.refererAppLink) {
return;
}
[self updateNavigationBarY:CGRectGetHeight(_view.bounds)];
}
- (void)updateNavigationBarY:(CGFloat)y {
UINavigationBar *navigationBar = _navigationController.navigationBar;
CGRect navigationBarFrame = navigationBar.frame;
CGFloat oldContainerViewY = CGRectGetMaxY(navigationBarFrame);
navigationBarFrame.origin.y = y;
navigationBar.frame = navigationBarFrame;
CGFloat dy = CGRectGetMaxY(navigationBarFrame) - oldContainerViewY;
UIView *containerView = _navigationController.visibleViewController.view.superview;
containerView.frame = UIEdgeInsetsInsetRect(containerView.frame, UIEdgeInsetsMake(dy, 0.0, 0.0, 0.0));
}
- (void)closeViewAnimated:(BOOL)animated {
[self closeViewAnimated:animated explicitlyClosed:YES];
}
- (void)closeViewAnimated:(BOOL)animated explicitlyClosed:(BOOL)explicitlyClosed {
void (^closer)(void) = ^{
if (_navigationController) {
[self updateNavigationBarY:_view.statusBarHeight];
}
CGRect frame = _view.frame;
frame.size.height = 0.0;
_view.frame = frame;
};
if (animated) {
[UIView animateWithDuration:kBFViewAnimationDuration animations:^{
closer();
} completion:^(BOOL finished) {
if (explicitlyClosed) {
_view.closed = YES;
}
}];
} else {
closer();
if (explicitlyClosed) {
_view.closed = YES;
}
}
}
- (void)openRefererAppLink:(BFAppLink *)refererAppLink {
if (refererAppLink) {
id<BFAppLinkReturnToRefererControllerDelegate> delegate = _delegate;
if ([delegate respondsToSelector:@selector(returnToRefererController:willNavigateToAppLink:)]) {
[delegate returnToRefererController:self willNavigateToAppLink:refererAppLink];
}
NSError *error = nil;
BFAppLinkNavigationType type = [BFAppLinkNavigation navigateToAppLink:refererAppLink error:&error];
if ([delegate respondsToSelector:@selector(returnToRefererController:didNavigateToAppLink:type:)]) {
[delegate returnToRefererController:self didNavigateToAppLink:refererAppLink type:type];
}
}
}
@end
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <Bolts/BFAppLinkNavigation.h>
@class BFAppLinkReturnToRefererView;
@class BFURL;
typedef NS_ENUM(NSUInteger, BFIncludeStatusBarInSize) {
BFIncludeStatusBarInSizeNever,
BFIncludeStatusBarInSizeIOS7AndLater,
BFIncludeStatusBarInSizeAlways,
};
/*!
Protocol that a class can implement in order to be notified when the user has navigated back
to the referer of an App Link.
*/
@protocol BFAppLinkReturnToRefererViewDelegate <NSObject>
/*!
Called when the user has tapped inside the close button.
*/
- (void)returnToRefererViewDidTapInsideCloseButton:(BFAppLinkReturnToRefererView *)view;
/*!
Called when the user has tapped inside the App Link portion of the view.
*/
- (void)returnToRefererViewDidTapInsideLink:(BFAppLinkReturnToRefererView *)view
link:(BFAppLink *)link;
@end
/*!
Provides a UIView that displays a button allowing users to navigate back to the
application that launched the App Link currently being handled, if the App Link
contained referer data. The user can also close the view by clicking a close button
rather than navigating away. If the view is provided an App Link that does not contain
referer data, it will have zero size and no UI will be displayed.
*/
@interface BFAppLinkReturnToRefererView : UIView
/*!
The delegate that will be notified when the user navigates back to the referer.
*/
@property (nonatomic, weak) id<BFAppLinkReturnToRefererViewDelegate> delegate;
/*!
The color of the text label and close button.
*/
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong) BFAppLink *refererAppLink;
/*!
Indicates whether to extend the size of the view to include the current status bar
size, for use in scenarios where the view might extend under the status bar on iOS 7 and
above; this property has no effect on earlier versions of iOS.
*/
@property (nonatomic, assign) BFIncludeStatusBarInSize includeStatusBarInSize;
/*!
Indicates whether the user has closed the view by clicking the close button.
*/
@property (nonatomic, assign) BOOL closed;
@end
@@ -0,0 +1,269 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFAppLinkReturnToRefererView.h"
#import "BFAppLink.h"
#import "BFAppLinkTarget.h"
static const CGFloat BFMarginX = 8.5f;
static const CGFloat BFMarginY = 8.5f;
static NSString *const BFRefererAppLink = @"referer_app_link";
static NSString *const BFRefererAppName = @"app_name";
static NSString *const BFRefererUrl = @"url";
static const CGFloat BFCloseButtonWidth = 12.0;
static const CGFloat BFCloseButtonHeight = 12.0;
@interface BFAppLinkReturnToRefererView ()
@property (nonatomic, strong) UILabel *labelView;
@property (nonatomic, strong) UIButton *closeButton;
@property (nonatomic, strong) UITapGestureRecognizer *insideTapGestureRecognizer;
@property (nonatomic, strong) UIView *viewToMoveWithNavController;
@end
@implementation BFAppLinkReturnToRefererView {
BOOL _explicitlyHidden;
}
#pragma mark - Initialization
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
[self sizeToFit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit {
// Initialization code
_includeStatusBarInSize = BFIncludeStatusBarInSizeIOS7AndLater;
// iOS 7 system blue color
self.backgroundColor = [UIColor colorWithRed:0.0f green:122.0f / 255.0f blue:1.0f alpha:1.0f];
self.textColor = [UIColor whiteColor];
self.clipsToBounds = YES;
[self initViews];
}
- (void)initViews {
if (!_labelView && !_closeButton) {
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
_closeButton.backgroundColor = [UIColor clearColor];
_closeButton.userInteractionEnabled = YES;
_closeButton.clipsToBounds = YES;
_closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
_closeButton.contentMode = UIViewContentModeCenter;
[_closeButton addTarget:self action:@selector(closeButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_closeButton];
_labelView = [[UILabel alloc] initWithFrame:CGRectZero];
_labelView.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
_labelView.textColor = [UIColor whiteColor];
_labelView.backgroundColor = [UIColor clearColor];
#ifdef __IPHONE_6_0
_labelView.textAlignment = NSTextAlignmentCenter;
#else
_labelView.textAlignment = UITextAlignmentCenter;
#endif
_labelView.clipsToBounds = YES;
[self updateLabelText];
[self addSubview:_labelView];
_insideTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapInside:)];
_labelView.userInteractionEnabled = YES;
[_labelView addGestureRecognizer:_insideTapGestureRecognizer];
[self updateColors];
}
}
#pragma mark - Layout
- (CGSize)intrinsicContentSize {
CGSize size = self.bounds.size;
if (_closed || !self.hasRefererData) {
size.height = 0.0;
} else {
CGSize labelSize = [_labelView sizeThatFits:size];
size = CGSizeMake(size.width, labelSize.height + 2 * BFMarginY + self.statusBarHeight);
}
return size;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect bounds = self.bounds;
_labelView.preferredMaxLayoutWidth = _labelView.bounds.size.width;
CGSize labelSize = [_labelView sizeThatFits:bounds.size];
_labelView.frame = CGRectMake(BFMarginX,
CGRectGetMaxY(bounds) - labelSize.height - 1.5f * BFMarginY,
CGRectGetMaxX(bounds) - BFCloseButtonWidth - 3 * BFMarginX,
labelSize.height + BFMarginY);
_closeButton.frame = CGRectMake(CGRectGetMaxX(bounds) - BFCloseButtonWidth - 2 * BFMarginX,
_labelView.center.y - BFCloseButtonHeight / 2.0f - BFMarginY,
BFCloseButtonWidth + 2 * BFMarginX,
BFCloseButtonHeight + 2 * BFMarginY);
}
- (CGSize)sizeThatFits:(CGSize)size {
if (_closed || !self.hasRefererData) {
size = CGSizeMake(size.width, 0.0);
} else {
CGSize labelSize = [_labelView sizeThatFits:size];
size = CGSizeMake(size.width, labelSize.height + 2 * BFMarginY + self.statusBarHeight);
}
return size;
}
- (CGFloat)statusBarHeight {
UIApplication *application = [UIApplication sharedApplication];
BOOL include;
switch (_includeStatusBarInSize) {
case BFIncludeStatusBarInSizeAlways:
include = YES;
break;
case BFIncludeStatusBarInSizeIOS7AndLater: {
float systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
include = (systemVersion >= 7.0);
break;
}
case BFIncludeStatusBarInSizeNever:
include = NO;
break;
}
if (include && !application.statusBarHidden) {
BOOL landscape = UIInterfaceOrientationIsLandscape(application.statusBarOrientation);
CGRect statusBarFrame = application.statusBarFrame;
return landscape ? CGRectGetWidth(statusBarFrame) : CGRectGetHeight(statusBarFrame);
}
return 0;
}
#pragma mark - Public API
- (void)setIncludeStatusBarInSize:(BFIncludeStatusBarInSize)includeStatusBarInSize {
_includeStatusBarInSize = includeStatusBarInSize;
[self setNeedsLayout];
[self invalidateIntrinsicContentSize];
}
- (void)setTextColor:(UIColor *)textColor {
_textColor = textColor;
[self updateColors];
}
- (void)setRefererAppLink:(BFAppLink *)refererAppLink {
_refererAppLink = refererAppLink;
[self updateLabelText];
[self updateHidden];
[self invalidateIntrinsicContentSize];
}
- (void)setClosed:(BOOL)closed {
if (_closed != closed) {
_closed = closed;
[self updateHidden];
[self invalidateIntrinsicContentSize];
}
}
- (void)setHidden:(BOOL)hidden {
_explicitlyHidden = hidden;
[self updateHidden];
}
#pragma mark - Private
- (void)updateLabelText {
NSString *appName = (_refererAppLink && _refererAppLink.targets[0]) ? [_refererAppLink.targets[0] appName] : nil;
_labelView.text = [self localizedLabelForReferer:appName];
}
- (void)updateColors {
UIImage *closeButtonImage = [self drawCloseButtonImageWithColor:_textColor];
_labelView.textColor = _textColor;
[_closeButton setImage:closeButtonImage forState:UIControlStateNormal];
}
- (UIImage *)drawCloseButtonImageWithColor:(UIColor *)color {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(BFCloseButtonWidth, BFCloseButtonHeight), NO, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextSetLineWidth(context, 1.25f);
CGFloat inset = 0.5f;
CGContextMoveToPoint(context, inset, inset);
CGContextAddLineToPoint(context, BFCloseButtonWidth - inset, BFCloseButtonHeight - inset);
CGContextStrokePath(context);
CGContextMoveToPoint(context, BFCloseButtonWidth - inset, inset);
CGContextAddLineToPoint(context, inset, BFCloseButtonHeight - inset);
CGContextStrokePath(context);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
- (NSString *)localizedLabelForReferer:(NSString *)refererName {
if (!refererName) {
return nil;
}
NSString *format = NSLocalizedString(@"Touch to return to %1$@", @"Format for the string to return to a calling app.");
return [NSString stringWithFormat:format, refererName];
}
- (BOOL)hasRefererData {
return _refererAppLink && _refererAppLink.targets[0];
}
- (void)closeButtonTapped:(id)sender {
[_delegate returnToRefererViewDidTapInsideCloseButton:self];
}
- (void)onTapInside:(UIGestureRecognizer *)sender {
[_delegate returnToRefererViewDidTapInsideLink:self link:_refererAppLink];
}
- (void)updateHidden {
[super setHidden:_explicitlyHidden || _closed || !self.hasRefererData];
}
@end
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Bolts/BFAppLinkReturnToRefererView.h>
@interface BFAppLinkReturnToRefererView (Internal)
- (CGFloat)statusBarHeight;
@end
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
/*!
Represents a target defined in App Link metadata, consisting of at least
a URL, and optionally an App Store ID and name.
*/
@interface BFAppLinkTarget : NSObject
/*! Creates a BFAppLinkTarget with the given app site and target URL. */
+ (instancetype)appLinkTargetWithURL:(NSURL *)url
appStoreId:(NSString *)appStoreId
appName:(NSString *)appName;
/*! The URL prefix for this app link target */
@property (nonatomic, strong, readonly) NSURL *URL;
/*! The app ID for the app store */
@property (nonatomic, copy, readonly) NSString *appStoreId;
/*! The name of the app */
@property (nonatomic, copy, readonly) NSString *appName;
@end
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFAppLinkTarget.h"
@interface BFAppLinkTarget ()
@property (nonatomic, strong, readwrite) NSURL *URL;
@property (nonatomic, copy, readwrite) NSString *appStoreId;
@property (nonatomic, copy, readwrite) NSString *appName;
@end
@implementation BFAppLinkTarget
+ (instancetype)appLinkTargetWithURL:(NSURL *)url
appStoreId:(NSString *)appStoreId
appName:(NSString *)appName {
BFAppLinkTarget *target = [[self alloc] init];
target.URL = url;
target.appStoreId = appStoreId;
target.appName = appName;
return target;
}
@end
+32
View File
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Bolts/BFAppLink.h>
FOUNDATION_EXPORT NSString *const BFAppLinkDataParameterName;
FOUNDATION_EXPORT NSString *const BFAppLinkTargetKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkUserAgentKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkExtrasKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkVersionKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkRefererAppLink;
FOUNDATION_EXPORT NSString *const BFAppLinkRefererAppName;
FOUNDATION_EXPORT NSString *const BFAppLinkRefererUrl;
@interface BFAppLink (Internal)
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
targets:(NSArray *)targets
webURL:(NSURL *)webURL
isBackToReferrer:(BOOL)isBackToReferrer;
/*! return if this AppLink is to go back to referrer. */
@property (nonatomic, assign, readonly, getter=isBackToReferrer) BOOL backToReferrer;
@end
+45
View File
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
/*! The name of the notification posted by BFMeasurementEvent */
FOUNDATION_EXPORT NSString *const BFMeasurementEventNotificationName;
/*! Defines keys in the userInfo object for the notification named BFMeasurementEventNotificationName */
/*! The string field for the name of the event */
FOUNDATION_EXPORT NSString *const BFMeasurementEventNameKey;
/*! The dictionary field for the arguments of the event */
FOUNDATION_EXPORT NSString *const BFMeasurementEventArgsKey;
/*! Bolts Events raised by BFMeasurementEvent for Applink */
/*!
The name of the event posted when [BFURL URLWithURL:] is called successfully. This represents the successful parsing of an app link URL.
*/
FOUNDATION_EXPORT NSString *const BFAppLinkParseEventName;
/*!
The name of the event posted when [BFURL URLWithInboundURL:] is called successfully.
This represents parsing an inbound app link URL from a different application
*/
FOUNDATION_EXPORT NSString *const BFAppLinkNavigateInEventName;
/*! The event raised when the user navigates from your app to other apps */
FOUNDATION_EXPORT NSString *const BFAppLinkNavigateOutEventName;
/*!
The event raised when the user navigates out from your app and back to the referrer app.
e.g when the user leaves your app after tapping the back-to-referrer navigation bar
*/
FOUNDATION_EXPORT NSString *const BFAppLinkNavigateBackToReferrerEventName;
@interface BFMeasurementEvent : NSObject
@end
+62
View File
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFMeasurementEvent_Internal.h"
NSString *const BFMeasurementEventNotificationName = @"com.parse.bolts.measurement_event";
NSString *const BFMeasurementEventNameKey = @"event_name";
NSString *const BFMeasurementEventArgsKey = @"event_args";
/* app Link Event raised by this BFURL */
NSString *const BFAppLinkParseEventName = @"al_link_parse";
NSString *const BFAppLinkNavigateInEventName = @"al_nav_in";
/*! AppLink events raised in this class */
NSString *const BFAppLinkNavigateOutEventName = @"al_nav_out";
NSString *const BFAppLinkNavigateBackToReferrerEventName = @"al_ref_back_out";
__attribute__((noinline)) void warnOnMissingEventName() {
NSLog(@"Warning: Missing event name when logging bolts measurement event. \n"
" Ignoring this event in logging.");
}
@implementation BFMeasurementEvent {
NSString *_name;
NSDictionary *_args;
}
- (void)postNotification {
if (!_name) {
warnOnMissingEventName();
return;
}
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{BFMeasurementEventNameKey : _name,
BFMeasurementEventArgsKey : _args};
[center postNotificationName:BFMeasurementEventNotificationName
object:self
userInfo:userInfo];
}
- (instancetype)initEventWithName:(NSString *)name args:(NSDictionary *)args {
if ((self = [super init])) {
_name = name;
_args = args ? args : @{};
}
return self;
}
+ (void)postNotificationForEventName:(NSString *)name args:(NSDictionary *)args {
[[[self alloc] initEventWithName:name args:args] postNotification];
}
@end
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Bolts/BFMeasurementEvent.h>
/*!
Provides methods for posting notifications from the Bolts framework
*/
@interface BFMeasurementEvent (Internal)
+ (void)postNotificationForEventName:(NSString *)name args:(NSDictionary *)args;
@end
+75
View File
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
@class BFAppLink;
/*!
Provides a set of utilities for working with NSURLs, such as parsing of query parameters
and handling for App Link requests.
*/
@interface BFURL : NSObject
/*!
Creates a link target from a raw URL.
On success, this posts the BFAppLinkParseEventName measurement event. If you are constructing the BFURL within your application delegate's
application:openURL:sourceApplication:annotation:, you should instead use URLWithInboundURL:sourceApplication:
to support better BFMeasurementEvent notifications
@param url The instance of `NSURL` to create BFURL from.
*/
+ (BFURL *)URLWithURL:(NSURL *)url;
/*!
Creates a link target from a raw URL received from an external application. This is typically called from the app delegate's
application:openURL:sourceApplication:annotation: and will post the BFAppLinkNavigateInEventName measurement event.
@param url The instance of `NSURL` to create BFURL from.
@param sourceApplication the bundle ID of the app that is requesting your app to open the URL. The same sourceApplication in application:openURL:sourceApplication:annotation:
*/
+ (BFURL *)URLWithInboundURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication;
/*!
Gets the target URL. If the link is an App Link, this is the target of the App Link.
Otherwise, it is the url that created the target.
*/
@property (nonatomic, strong, readonly) NSURL *targetURL;
/*!
Gets the query parameters for the target, parsed into an NSDictionary.
*/
@property (nonatomic, strong, readonly) NSDictionary *targetQueryParameters;
/*!
If this link target is an App Link, this is the data found in al_applink_data.
Otherwise, it is nil.
*/
@property (nonatomic, strong, readonly) NSDictionary *appLinkData;
/*!
If this link target is an App Link, this is the data found in extras.
*/
@property (nonatomic, strong, readonly) NSDictionary *appLinkExtras;
/*!
The App Link indicating how to navigate back to the referer app, if any.
*/
@property (nonatomic, strong, readonly) BFAppLink *appLinkReferer;
/*!
The URL that was used to create this BFURL.
*/
@property (nonatomic, strong, readonly) NSURL *inputURL;
/*!
The query parameters of the inputURL, parsed into an NSDictionary.
*/
@property (nonatomic, strong, readonly) NSDictionary *inputQueryParameters;
@end
+142
View File
@@ -0,0 +1,142 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFURL_Internal.h"
#import "BFAppLink_Internal.h"
#import "BFAppLinkTarget.h"
#import "BFMeasurementEvent_Internal.h"
@implementation BFURL
- (instancetype)initWithURL:(NSURL *)url forOpenInboundURL:(BOOL)forOpenURLEvent sourceApplication:(NSString *)sourceApplication forRenderBackToReferrerBar:(BOOL)forRenderBackToReferrerBar {
self = [super init];
if (!self) return nil;
_inputURL = url;
_targetURL = url;
// Parse the query string parameters for the base URL
NSDictionary *baseQuery = [BFURL queryParametersForURL:url];
_inputQueryParameters = baseQuery;
_targetQueryParameters = baseQuery;
// Check for applink_data
NSString *appLinkDataString = baseQuery[BFAppLinkDataParameterName];
if (appLinkDataString) {
// Try to parse the JSON
NSError *error = nil;
NSDictionary *applinkData = [NSJSONSerialization JSONObjectWithData:[appLinkDataString dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&error];
if (!error && [applinkData isKindOfClass:[NSDictionary class]]) {
// If the version is not specified, assume it is 1.
NSString *version = applinkData[BFAppLinkVersionKeyName] ?: @"1.0";
NSString *target = applinkData[BFAppLinkTargetKeyName];
if ([version isKindOfClass:[NSString class]] &&
[version isEqual:BFAppLinkVersion]) {
// There's applink data! The target should actually be the applink target.
_appLinkData = applinkData;
id applinkExtras = applinkData[BFAppLinkExtrasKeyName];
if (applinkExtras && [applinkExtras isKindOfClass:[NSDictionary class]]) {
_appLinkExtras = applinkExtras;
}
_targetURL = ([target isKindOfClass:[NSString class]] ? [NSURL URLWithString:target] : url);
_targetQueryParameters = [BFURL queryParametersForURL:_targetURL];
NSDictionary *refererAppLink = _appLinkData[BFAppLinkRefererAppLink];
NSString *refererURLString = refererAppLink[BFAppLinkRefererUrl];
NSString *refererAppName = refererAppLink[BFAppLinkRefererAppName];
if (refererURLString && refererAppName) {
BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:refererURLString]
appStoreId:nil
appName:refererAppName];
_appLinkReferer = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:refererURLString]
targets:@[ target ]
webURL:nil
isBackToReferrer:YES];
}
// Raise Measurement Event
NSString *const EVENT_YES_VAL = @"1";
NSString *const EVENT_NO_VAL = @"0";
NSMutableDictionary *logData = [[NSMutableDictionary alloc] init];
logData[@"version"] = version;
if (refererURLString) {
logData[@"refererURL"] = refererURLString;
}
if (refererAppName) {
logData[@"refererAppName"] = refererAppName;
}
if (sourceApplication) {
logData[@"sourceApplication"] = sourceApplication;
}
if ([_targetURL absoluteString]) {
logData[@"targetURL"] = [_targetURL absoluteString];
}
if ([_inputURL absoluteString]) {
logData[@"inputURL"] = [_inputURL absoluteString];
}
if ([_inputURL scheme]) {
logData[@"inputURLScheme"] = [_inputURL scheme];
}
logData[@"forRenderBackToReferrerBar"] = forRenderBackToReferrerBar ? EVENT_YES_VAL : EVENT_NO_VAL;
logData[@"forOpenUrl"] = forOpenURLEvent ? EVENT_YES_VAL : EVENT_NO_VAL;
[BFMeasurementEvent postNotificationForEventName:BFAppLinkParseEventName args:logData];
if (forOpenURLEvent) {
[BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateInEventName args:logData];
}
}
}
}
return self;
}
+ (BFURL *)URLWithURL:(NSURL *)url {
return [[BFURL alloc] initWithURL:url forOpenInboundURL:NO sourceApplication:nil forRenderBackToReferrerBar:NO];
}
+ (BFURL *)URLWithInboundURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication {
return [[BFURL alloc] initWithURL:url forOpenInboundURL:YES sourceApplication:sourceApplication forRenderBackToReferrerBar:NO];
}
+ (BFURL *)URLForRenderBackToReferrerBarURL:(NSURL *)url {
return [[BFURL alloc] initWithURL:url forOpenInboundURL:NO sourceApplication:nil forRenderBackToReferrerBar:YES];
}
+ (NSString *)decodeURLString:(NSString *)string {
return (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapes(NULL,
(CFStringRef)string,
CFSTR("")));
}
+ (NSDictionary *)queryParametersForURL:(NSURL *)url {
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
NSString *query = url.query;
if ([query isEqualToString:@""]) {
return @{};
}
NSArray *queryComponents = [query componentsSeparatedByString:@"&"];
for (NSString *component in queryComponents) {
NSRange equalsLocation = [component rangeOfString:@"="];
if (equalsLocation.location == NSNotFound) {
// There's no equals, so associate the key with NSNull
parameters[[self decodeURLString:component]] = [NSNull null];
} else {
NSString *key = [self decodeURLString:[component substringToIndex:equalsLocation.location]];
NSString *value = [self decodeURLString:[component substringFromIndex:equalsLocation.location + 1]];
parameters[key] = value;
}
}
return [NSDictionary dictionaryWithDictionary:parameters];
}
@end
+15
View File
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Bolts/BFURL.h>
@interface BFURL (Internal)
+ (BFURL *)URLForRenderBackToReferrerBarURL:(NSURL *)url;
@end
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <Bolts/BFAppLinkResolving.h>
/*!
A reference implementation for an App Link resolver that uses a hidden UIWebView
to parse the HTML containing App Link metadata.
*/
@interface BFWebViewAppLinkResolver : NSObject <BFAppLinkResolving>
/*!
Gets the instance of a BFWebViewAppLinkResolver.
*/
+ (instancetype)sharedInstance;
@end
+302
View File
@@ -0,0 +1,302 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
#import "BFWebViewAppLinkResolver.h"
#import "BFAppLink.h"
#import "BFAppLinkTarget.h"
#import "BFTask.h"
#import "BFTaskCompletionSource.h"
#import "BFExecutor.h"
// Defines JavaScript to extract app link tags from HTML content
static NSString *const BFWebViewAppLinkResolverTagExtractionJavaScript = @""
"(function() {"
" var metaTags = document.getElementsByTagName('meta');"
" var results = [];"
" for (var i = 0; i < metaTags.length; i++) {"
" var property = metaTags[i].getAttribute('property');"
" if (property && property.substring(0, 'al:'.length) === 'al:') {"
" var tag = { \"property\": metaTags[i].getAttribute('property') };"
" if (metaTags[i].hasAttribute('content')) {"
" tag['content'] = metaTags[i].getAttribute('content');"
" }"
" results.push(tag);"
" }"
" }"
" return JSON.stringify(results);"
"})()";
static NSString *const BFWebViewAppLinkResolverIOSURLKey = @"url";
static NSString *const BFWebViewAppLinkResolverIOSAppStoreIdKey = @"app_store_id";
static NSString *const BFWebViewAppLinkResolverIOSAppNameKey = @"app_name";
static NSString *const BFWebViewAppLinkResolverDictionaryValueKey = @"_value";
static NSString *const BFWebViewAppLinkResolverPreferHeader = @"Prefer-Html-Meta-Tags";
static NSString *const BFWebViewAppLinkResolverMetaTagPrefix = @"al";
static NSString *const BFWebViewAppLinkResolverWebKey = @"web";
static NSString *const BFWebViewAppLinkResolverIOSKey = @"ios";
static NSString *const BFWebViewAppLinkResolverIPhoneKey = @"iphone";
static NSString *const BFWebViewAppLinkResolverIPadKey = @"ipad";
static NSString *const BFWebViewAppLinkResolverWebURLKey = @"url";
static NSString *const BFWebViewAppLinkResolverShouldFallbackKey = @"should_fallback";
@interface BFWebViewAppLinkResolverWebViewDelegate : NSObject <UIWebViewDelegate>
@property (nonatomic, copy) void (^didFinishLoad)(UIWebView *webView);
@property (nonatomic, copy) void (^didFailLoadWithError)(UIWebView *webView, NSError *error);
@property (nonatomic, assign) BOOL hasLoaded;
@end
@implementation BFWebViewAppLinkResolverWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if (self.didFinishLoad) {
self.didFinishLoad(webView);
}
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
if (self.didFailLoadWithError) {
self.didFailLoadWithError(webView, error);
}
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (self.hasLoaded) {
// Consider loading a second resource to be "success", since it indicates an inner frame
// or redirect is happening. We can run the tag extraction script at this point.
self.didFinishLoad(webView);
return NO;
}
self.hasLoaded = YES;
return YES;
}
@end
@implementation BFWebViewAppLinkResolver
+ (instancetype)sharedInstance {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (BFTask *)followRedirects:(NSURL *)url {
// This task will be resolved with either the redirect NSURL
// or a dictionary with the response data to be returned.
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:BFWebViewAppLinkResolverMetaTagPrefix forHTTPHeaderField:BFWebViewAppLinkResolverPreferHeader];
void (^completion)(NSURLResponse *response, NSData *data, NSError *error) = ^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
[tcs setError:error];
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
// NSURLConnection usually follows redirects automatically, but the
// documentation is unclear what the default is. This helps it along.
if (httpResponse.statusCode >= 300 && httpResponse.statusCode < 400) {
NSString *redirectString = httpResponse.allHeaderFields[@"Location"];
NSURL *redirectURL = [NSURL URLWithString:redirectString];
[tcs setResult:redirectURL];
return;
}
}
[tcs setResult:@{ @"response" : response, @"data" : data }];
};
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
completion(response, data, error);
}] resume];
#else
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:completion];
#endif
return [tcs.task continueWithSuccessBlock:^id(BFTask *task) {
// If we redirected, just keep recursing.
if ([task.result isKindOfClass:[NSURL class]]) {
return [self followRedirects:task.result];
}
return task;
}];
}
- (BFTask *)appLinkFromURLInBackground:(NSURL *)url {
return [[self followRedirects:url] continueWithExecutor:[BFExecutor mainThreadExecutor]
withSuccessBlock:^id(BFTask *task) {
NSData *responseData = task.result[@"data"];
NSHTTPURLResponse *response = task.result[@"response"];
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
UIWebView *webView = [[UIWebView alloc] init];
BFWebViewAppLinkResolverWebViewDelegate *listener = [[BFWebViewAppLinkResolverWebViewDelegate alloc] init];
__block BFWebViewAppLinkResolverWebViewDelegate *retainedListener = listener;
listener.didFinishLoad = ^(UIWebView *view) {
if (retainedListener) {
NSDictionary *ogData = [self getALDataFromLoadedPage:view];
[view removeFromSuperview];
view.delegate = nil;
retainedListener = nil;
[tcs setResult:[self appLinkFromALData:ogData destination:url]];
}
};
listener.didFailLoadWithError = ^(UIWebView* view, NSError *error) {
if (retainedListener) {
[view removeFromSuperview];
view.delegate = nil;
retainedListener = nil;
[tcs setError:error];
}
};
webView.delegate = listener;
webView.hidden = YES;
[webView loadData:responseData
MIMEType:response.MIMEType
textEncodingName:response.textEncodingName
baseURL:response.URL];
UIWindow *window = [UIApplication sharedApplication].windows.firstObject;
[window addSubview:webView];
return tcs.task;
}];
}
/*
Builds up a data structure filled with the app link data from the meta tags on a page.
The structure of this object is a dictionary where each key holds an array of app link
data dictionaries. Values are stored in a key called "_value".
*/
- (NSDictionary *)parseALData:(NSArray *)dataArray {
NSMutableDictionary *al = [NSMutableDictionary dictionary];
for (NSDictionary *tag in dataArray) {
NSString *name = tag[@"property"];
if (![name isKindOfClass:[NSString class]]) {
continue;
}
NSArray *nameComponents = [name componentsSeparatedByString:@":"];
if (![nameComponents[0] isEqualToString:BFWebViewAppLinkResolverMetaTagPrefix]) {
continue;
}
NSMutableDictionary *root = al;
for (int i = 1; i < nameComponents.count; i++) {
NSMutableArray *children = root[nameComponents[i]];
if (!children) {
children = [NSMutableArray array];
root[nameComponents[i]] = children;
}
NSMutableDictionary *child = children.lastObject;
if (!child || i == nameComponents.count - 1) {
child = [NSMutableDictionary dictionary];
[children addObject:child];
}
root = child;
}
if (tag[@"content"]) {
root[BFWebViewAppLinkResolverDictionaryValueKey] = tag[@"content"];
}
}
return al;
}
- (NSDictionary *)getALDataFromLoadedPage:(UIWebView *)webView {
// Run some JavaScript in the webview to fetch the meta tags.
NSString *jsonString = [webView stringByEvaluatingJavaScriptFromString:BFWebViewAppLinkResolverTagExtractionJavaScript];
NSError *error = nil;
NSArray *arr = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&error];
return [self parseALData:arr];
}
/*
Converts app link data into a BFAppLink containing the targets relevant for this platform.
*/
- (BFAppLink *)appLinkFromALData:(NSDictionary *)appLinkDict destination:(NSURL *)destination {
NSMutableArray *linkTargets = [NSMutableArray array];
NSArray *platformData = nil;
switch (UI_USER_INTERFACE_IDIOM()) {
case UIUserInterfaceIdiomPad:
platformData = @[ appLinkDict[BFWebViewAppLinkResolverIPadKey] ?: @{},
appLinkDict[BFWebViewAppLinkResolverIOSKey] ?: @{} ];
break;
case UIUserInterfaceIdiomPhone:
platformData = @[ appLinkDict[BFWebViewAppLinkResolverIPhoneKey] ?: @{},
appLinkDict[BFWebViewAppLinkResolverIOSKey] ?: @{} ];
break;
#ifdef __TVOS_9_0
case UIUserInterfaceIdiomTV:
#endif
case UIUserInterfaceIdiomUnspecified:
default:
// Future-proofing. Other User Interface idioms should only hit ios.
platformData = @[ appLinkDict[BFWebViewAppLinkResolverIOSKey] ?: @{} ];
break;
}
for (NSArray *platformObjects in platformData) {
for (NSDictionary *platformDict in platformObjects) {
// The schema requires a single url/app store id/app name,
// but we could find multiple of them. We'll make a best effort
// to interpret this data.
NSArray *urls = platformDict[BFWebViewAppLinkResolverIOSURLKey];
NSArray *appStoreIds = platformDict[BFWebViewAppLinkResolverIOSAppStoreIdKey];
NSArray *appNames = platformDict[BFWebViewAppLinkResolverIOSAppNameKey];
NSUInteger maxCount = MAX(urls.count, MAX(appStoreIds.count, appNames.count));
for (NSUInteger i = 0; i < maxCount; i++) {
NSString *urlString = urls[i][BFWebViewAppLinkResolverDictionaryValueKey];
NSURL *url = urlString ? [NSURL URLWithString:urlString] : nil;
NSString *appStoreId = appStoreIds[i][BFWebViewAppLinkResolverDictionaryValueKey];
NSString *appName = appNames[i][BFWebViewAppLinkResolverDictionaryValueKey];
BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:url
appStoreId:appStoreId
appName:appName];
[linkTargets addObject:target];
}
}
}
NSDictionary *webDict = appLinkDict[BFWebViewAppLinkResolverWebKey][0];
NSString *webUrlString = webDict[BFWebViewAppLinkResolverWebURLKey][0][BFWebViewAppLinkResolverDictionaryValueKey];
NSString *shouldFallbackString = webDict[BFWebViewAppLinkResolverShouldFallbackKey][0][BFWebViewAppLinkResolverDictionaryValueKey];
NSURL *webUrl = destination;
if (shouldFallbackString &&
[@[ @"no", @"false", @"0" ] containsObject:[shouldFallbackString lowercaseString]]) {
webUrl = nil;
}
if (webUrl && webUrlString) {
webUrl = [NSURL URLWithString:webUrlString];
}
return [BFAppLink appLinkWithSourceURL:destination
targets:linkTargets
webURL:webUrl];
}
@end