Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Adds frictionless apprequest support

Summary:
Here are the mods to the sdk:
1. adds four methods to the Facebook class, for use by applications
  * enableFrictionlessRequests
  * reloadFrictionlessRecipientCache
  * isFrictionlessEnabledForRecipient
  * isFrictionlessEnabledForRecipients
2. extends dialog method and support class to support frictionless behavior for “apprequest” dialogs
3. extends Hacbook sample to add frictionless app-request support

Notes:
* enableFrictionlessRequests method of FBFrictionless turns on frictionless apprequest behavior in the SDK
* frictionless apprequest behavior means
  a.) a cache of allowed frictionless recipients is maintained
  b.) cache checked by dialog, and dialog is invisible for frictionless recipients
  c.) send responses are checked and recipient list is updated
  d.) logout and login cause clear and refetch of cache respectively
  • Loading branch information...
commit 4e3567c40e3b7f8156a82f18e5a2f5cd18e077eb 1 parent 9acf743
@onebit onebit authored
View
5 .arcconfig
@@ -0,0 +1,5 @@
+{
+ "project_id" : "facebook-ios-sdk",
+ "conduit_uri" : "https://phabricator.fb.com/api/",
+ "copyright_holder" : "Facebook"
+}
View
1  .gitignore
@@ -21,7 +21,6 @@ test/UnitTest/UnitTest.xcodeproj/*.mode*
test/UnitTest/build/
*~
*#
-.arcconfig
.DS_Store
project.xcworkspace
xcuserdata
View
6 sample/Hackbook/Hackbook.xcodeproj/project.pbxproj
@@ -38,6 +38,7 @@
30EA73FC13F5D590003DC0D2 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30EA73FB13F5D590003DC0D2 /* CoreLocation.framework */; };
30ED588C14358F8A00A226C3 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 30ED588B14358F8A00A226C3 /* Default@2x.png */; };
30ED588F14358F9400A226C3 /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 30ED588E14358F9400A226C3 /* Icon@2x.png */; };
+ 84E7D99614D9CC13006A6299 /* FBFrictionlessRequestSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 84E7D99514D9CC13006A6299 /* FBFrictionlessRequestSettings.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -92,6 +93,8 @@
30EA73FB13F5D590003DC0D2 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; };
30ED588B14358F8A00A226C3 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = "<group>"; };
30ED588E14358F9400A226C3 /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = "<group>"; };
+ 84E7D99414D9CC13006A6299 /* FBFrictionlessRequestSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FBFrictionlessRequestSettings.h; path = ../../src/FBFrictionlessRequestSettings.h; sourceTree = "<group>"; };
+ 84E7D99514D9CC13006A6299 /* FBFrictionlessRequestSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FBFrictionlessRequestSettings.m; path = ../../src/FBFrictionlessRequestSettings.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -197,6 +200,8 @@
30EA73D613F5D523003DC0D2 /* FBLoginDialog.m */,
30EA73D713F5D523003DC0D2 /* FBRequest.h */,
30EA73D813F5D523003DC0D2 /* FBRequest.m */,
+ 84E7D99414D9CC13006A6299 /* FBFrictionlessRequestSettings.h */,
+ 84E7D99514D9CC13006A6299 /* FBFrictionlessRequestSettings.m */,
30EA73D913F5D523003DC0D2 /* JSON */,
);
name = FBConnect;
@@ -310,6 +315,7 @@
30EA73F013F5D523003DC0D2 /* SBJsonBase.m in Sources */,
30EA73F113F5D523003DC0D2 /* SBJsonParser.m in Sources */,
30EA73F213F5D523003DC0D2 /* SBJsonWriter.m in Sources */,
+ 84E7D99614D9CC13006A6299 /* FBFrictionlessRequestSettings.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
46 sample/Hackbook/Hackbook/APICallsViewController.m
@@ -540,6 +540,28 @@ - (void)getUserFriendTargetDialogRequest {
}
/*
+ * API: Enable frictionless in the SDK, retrieve friends enabled for frictionless send
+ */
+- (void)enableFrictionlessAppRequests {
+ HackbookAppDelegate *delegate =
+ (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
+
+ // Enable frictionless app requests
+ [[delegate facebook] enableFrictionlessRequests];
+
+ UIAlertView *alertView = [[UIAlertView alloc]
+ initWithTitle:@"Enabled Frictionless Requests"
+ message:@"Request actions such as\n"
+ @"Send Request and Send Invite\n"
+ @"now support frictionless behavior."
+ delegate:self
+ cancelButtonTitle:@"OK"
+ otherButtonTitles:nil,
+ nil];
+ [alertView show];
+}
+
+/*
* --------------------------------------------------------------------------
* Graph API
* --------------------------------------------------------------------------
@@ -939,12 +961,13 @@ - (void)request:(FBRequest *)request didLoad:(id)result {
}
case kAPIFriendsForDialogFeed:
{
- NSArray *resultData = [result objectForKey:@"data"];
+ NSArray *resultData = [result objectForKey: @"data"];
// Check that the user has friends
if ([resultData count] > 0) {
// Pick a random friend to post the feed to
int randomNumber = arc4random() % [resultData count];
- [self apiDialogFeedFriend:[[resultData objectAtIndex:randomNumber] objectForKey:@"id"]];
+ [self apiDialogFeedFriend:
+ [[resultData objectAtIndex: randomNumber] objectForKey: @"id"]];
} else {
[self showMessage:@"You do not have any friends to post to."];
}
@@ -1015,17 +1038,24 @@ - (void)request:(FBRequest *)request didLoad:(id)result {
}
case kAPIFriendsForTargetDialogRequests:
{
- NSArray *resultData = [result objectForKey:@"data"];
- if ([resultData count] > 0) {
- [self apiDialogRequestsSendTarget:[[resultData objectAtIndex:0] objectForKey:@"id"]];
+ NSArray *resultData = [result objectForKey: @"data"];
+ // got friends?
+ if ([resultData count] > 0) {
+ // pick a random one to send a request to
+ int randomIndex = arc4random() % [resultData count];
+ NSString* randomFriend =
+ [[resultData objectAtIndex: randomIndex] objectForKey: @"id"];
+ [self apiDialogRequestsSendTarget:randomFriend];
} else {
- [self showMessage:@"You have no friends to select."];
+ [self showMessage: @"You have no friends to select."];
}
break;
}
case kAPIGraphMe:
{
- NSString *nameID = [[NSString alloc] initWithFormat:@"%@ (%@)", [result objectForKey:@"name"], [result objectForKey:@"id"]];
+ NSString *nameID = [[NSString alloc] initWithFormat: @"%@ (%@)",
+ [result objectForKey:@"name"],
+ [result objectForKey:@"id"]];
NSMutableArray *userData = [[NSMutableArray alloc] initWithObjects:
[NSDictionary dictionaryWithObjectsAndKeys:
[result objectForKey:@"id"], @"id",
@@ -1162,7 +1192,7 @@ - (void)dialogCompleteWithUrl:(NSURL *)url {
// Successful requests return one or more request_ids.
// Get any request IDs, will be in the URL in the form
// request_ids[0]=1001316103543&request_ids[1]=10100303657380180
- NSMutableArray *requestIDs = [[NSMutableArray alloc] init];
+ NSMutableArray *requestIDs = [[[NSMutableArray alloc] init] autorelease];
for (NSString *paramKey in params) {
if ([paramKey hasPrefix:@"request_ids"]) {
[requestIDs addObject:[params objectForKey:paramKey]];
View
10 sample/Hackbook/Hackbook/DataSet.m
@@ -101,13 +101,20 @@ - (id)init {
@"Send request", @"button",
@"getUserFriendTargetDialogRequest", @"method",
nil];
+ NSDictionary *requestMenu5 = [[NSDictionary alloc] initWithObjectsAndKeys:
+ @"Enable frictionless requests", @"title",
+ @"To enable a no-prompt request and invite experience, enable frictionless requests.", @"description",
+ @"Enable frictionless", @"button",
+ @"enableFrictionlessAppRequests", @"method",
+ nil];
NSArray *requestMenuItems = [[NSArray alloc] initWithObjects:
requestMenu1,
requestMenu2,
requestMenu3,
requestMenu4,
- nil];
+ requestMenu5,
+ nil];
NSDictionary *requestConfigData = [[NSDictionary alloc] initWithObjectsAndKeys:
@"Requests", @"title",
@@ -122,6 +129,7 @@ - (id)init {
[requestMenu2 release];
[requestMenu3 release];
[requestMenu4 release];
+ [requestMenu5 release];
[requestMenuItems release];
[requestConfigData release];
View
14 sample/Hackbook/Hackbook/HackbookAppDelegate.m
@@ -49,28 +49,28 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
self.navigationController = navController;
[rootViewController release];
[navController release];
-
+
// Initialize Facebook
facebook = [[Facebook alloc] initWithAppId:kAppId andDelegate:rootViewController];
-
+
// Check and retrieve authorization information
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:@"FBAccessTokenKey"] && [defaults objectForKey:@"FBExpirationDateKey"]) {
facebook.accessToken = [defaults objectForKey:@"FBAccessTokenKey"];
facebook.expirationDate = [defaults objectForKey:@"FBExpirationDateKey"];
}
-
+
// Initialize API data (for views, etc.)
apiData = [[DataSet alloc] init];
-
+
// Initialize user permissions
userPermissions = [[NSMutableDictionary alloc] initWithCapacity:1];
-
+
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
-
+
// Check App ID:
// This is really a warning for the developer, this should not
// happen in a completed app
@@ -119,7 +119,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
[alertView release];
}
}
-
+
return YES;
}
View
70 sample/Hackbook/Hackbook/RootViewController.m
@@ -44,7 +44,7 @@ - (void)dealloc {
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
-
+
// Release any cached data, images, etc that aren't in use.
}
@@ -61,9 +61,9 @@ - (void)apiFQLIMe {
nil];
HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
[[delegate facebook] requestWithMethodName:@"fql.query"
- andParams:params
- andHttpMethod:@"POST"
- andDelegate:self];
+ andParams:params
+ andHttpMethod:@"POST"
+ andDelegate:self];
}
- (void)apiGraphUserPermissions {
@@ -80,11 +80,11 @@ - (void)apiGraphUserPermissions {
- (void)showLoggedIn {
[self.navigationController setNavigationBarHidden:NO animated:NO];
-
+
self.backgroundImageView.hidden = YES;
loginButton.hidden = YES;
self.menuTableView.hidden = NO;
-
+
[self apiFQLIMe];
}
@@ -94,16 +94,16 @@ - (void)showLoggedIn {
- (void)showLoggedOut {
[self.navigationController setNavigationBarHidden:YES animated:NO];
-
+
self.menuTableView.hidden = YES;
self.backgroundImageView.hidden = NO;
loginButton.hidden = NO;
-
+
// Clear personal info
nameLabel.text = @"";
// Get the profile image
[profilePhotoImageView setImage:nil];
-
+
[[self navigationController] popToRootViewControllerAnimated:YES];
}
@@ -137,7 +137,7 @@ - (void)menuButtonClicked:(id)sender {
// From this object we can then read the tag property to determine
// which menu button was clicked.
APICallsViewController *controller = [[APICallsViewController alloc]
- initWithIndex:[sender tag]];
+ initWithIndex:[sender tag]];
pendingApiCallsController = controller;
[self.navigationController pushViewController:controller animated:YES];
[controller release];
@@ -151,10 +151,10 @@ - (void)loadView {
[view setBackgroundColor:[UIColor whiteColor]];
self.view = view;
[view release];
-
+
// Initialize permissions
permissions = [[NSArray alloc] initWithObjects:@"offline_access", nil];
-
+
// Main menu items
mainMenuItems = [[NSMutableArray alloc] initWithCapacity:1];
HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
@@ -162,27 +162,27 @@ - (void)loadView {
for (NSUInteger i=0; i < [apiInfo count]; i++) {
[mainMenuItems addObject:[[apiInfo objectAtIndex:i] objectForKey:@"title"]];
}
-
+
// Set up the view programmatically
self.view.backgroundColor = [UIColor whiteColor];
-
+
self.navigationItem.title = @"Hackbook for iOS";
-
+
self.navigationItem.backBarButtonItem =
[[[UIBarButtonItem alloc] initWithTitle:@"Back"
style:UIBarButtonItemStyleBordered
target:nil
action:nil] autorelease];
-
+
// Background Image
backgroundImageView = [[UIImageView alloc]
- initWithFrame:CGRectMake(0,0,
- self.view.bounds.size.width,
- self.view.bounds.size.height)];
+ initWithFrame:CGRectMake(0,0,
+ self.view.bounds.size.width,
+ self.view.bounds.size.height)];
[backgroundImageView setImage:[UIImage imageNamed:@"Default.png"]];
//[backgroundImageView setAlpha:0.25];
[self.view addSubview:backgroundImageView];
-
+
// Login Button
loginButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
CGFloat xLoginButtonOffset = self.view.center.x - (318/2);
@@ -199,7 +199,7 @@ - (void)loadView {
forState:UIControlStateHighlighted];
[loginButton sizeToFit];
[self.view addSubview:loginButton];
-
+
// Main Menu Table
menuTableView = [[UITableView alloc] initWithFrame:self.view.bounds
style:UITableViewStylePlain];
@@ -210,7 +210,7 @@ - (void)loadView {
menuTableView.delegate = self;
menuTableView.hidden = YES;
//[self.view addSubview:menuTableView];
-
+
// Table header
headerView = [[UIView alloc]
initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 100)];
@@ -228,9 +228,9 @@ - (void)loadView {
nameLabel.text = @"";
[headerView addSubview:nameLabel];
menuTableView.tableHeaderView = headerView;
-
+
[self.view addSubview:menuTableView];
-
+
pendingApiCallsController = nil;
}
@@ -243,7 +243,7 @@ - (void)viewDidUnload {
- (void)viewWillAppear:(BOOL)animated {
//[self.navigationController setNavigationBarHidden:YES animated:animated];
[super viewWillAppear:animated];
-
+
HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
if (![[delegate facebook] isSessionValid]) {
[self showLoggedOut];
@@ -279,13 +279,13 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
-
+
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
-
+
//create the button
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(20, 20, (cell.contentView.frame.size.width-40), 44);
@@ -299,12 +299,12 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N
[button addTarget:self action:@selector(menuButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
button.tag = indexPath.row;
[cell.contentView addSubview:button];
-
+
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-
+
}
- (void)storeAuthData:(NSString *)accessToken expiresAt:(NSDate *)expiresAt {
@@ -320,10 +320,10 @@ - (void)storeAuthData:(NSString *)accessToken expiresAt:(NSDate *)expiresAt {
*/
- (void)fbDidLogin {
[self showLoggedIn];
-
+
HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
[self storeAuthData:[[delegate facebook] accessToken] expiresAt:[[delegate facebook] expirationDate]];
-
+
[pendingApiCallsController userDidGrantPermission];
}
@@ -344,14 +344,14 @@ -(void)fbDidNotLogin:(BOOL)cancelled {
*/
- (void)fbDidLogout {
pendingApiCallsController = nil;
-
+
// Remove saved authorization information if it exists and it is
// ok to clear it (logout, session invalid, app unauthorized)
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:@"FBAccessTokenKey"];
[defaults removeObjectForKey:@"FBExpirationDateKey"];
[defaults synchronize];
-
+
[self showLoggedOut];
}
@@ -403,7 +403,7 @@ - (void)request:(FBRequest *)request didLoad:(id)result {
nameLabel.text = [result objectForKey:@"name"];
// Get the profile image
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[result objectForKey:@"pic"]]]];
-
+
// Resize, crop the image to make sure it is square and renders
// well on Retina display
float ratio;
@@ -428,7 +428,7 @@ - (void)request:(FBRequest *)request didLoad:(id)result {
[image drawInRect:clipRect];
UIImage *imgThumb = UIGraphicsGetImageFromCurrentImageContext();
[imgThumb retain];
-
+
[profilePhotoImageView setImage:imgThumb];
[self apiGraphUserPermissions];
} else {
View
33 src/FBDialog.h
@@ -6,18 +6,19 @@
* 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 <UIKit/UIKit.h>
@protocol FBDialogDelegate;
+@class FBFrictionlessRequestSettings;
/**
* Do not use this interface directly, instead, use dialog in Facebook.h
@@ -26,18 +27,20 @@
*/
@interface FBDialog : UIView <UIWebViewDelegate> {
- id<FBDialogDelegate> _delegate;
- NSMutableDictionary *_params;
- NSString * _serverURL;
- NSURL* _loadingURL;
- UIWebView* _webView;
- UIActivityIndicatorView* _spinner;
- UIButton* _closeButton;
- UIInterfaceOrientation _orientation;
- BOOL _showingKeyboard;
-
- // Ensures that UI elements behind the dialog are disabled.
- UIView* _modalBackgroundView;
+ id<FBDialogDelegate> _delegate;
+ NSMutableDictionary *_params;
+ NSString * _serverURL;
+ NSURL* _loadingURL;
+ UIWebView* _webView;
+ UIActivityIndicatorView* _spinner;
+ UIButton* _closeButton;
+ UIInterfaceOrientation _orientation;
+ BOOL _showingKeyboard;
+ BOOL _isViewInvisible;
+ FBFrictionlessRequestSettings* _frictionlessSettings;
+
+ // Ensures that UI elements behind the dialog are disabled.
+ UIView* _modalBackgroundView;
}
/**
@@ -54,6 +57,8 @@
- (id)initWithURL: (NSString *) loadingURL
params: (NSMutableDictionary *) params
+ isViewInvisible: (BOOL) isViewInvisible
+ frictionlessSettings: (FBFrictionlessRequestSettings *) frictionlessSettings
delegate: (id <FBDialogDelegate>) delegate;
/**
View
154 src/FBDialog.m
@@ -17,6 +17,8 @@
#import "FBDialog.h"
#import "Facebook.h"
+#import "FBFrictionlessRequestSettings.h"
+#import "JSON.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
// global
@@ -263,7 +265,13 @@ - (void)postDismissCleanup {
- (void)dismiss:(BOOL)animated {
[self dialogWillDisappear];
-
+
+ // If the dialog has been closed, then we need to cancel the order to open it.
+ // This happens in the case of a frictionless request, see webViewDidFinishLoad for details
+ [NSObject cancelPreviousPerformRequestsWithTarget:self
+ selector:@selector(showWebView)
+ object:nil];
+
[_loadingURL release];
_loadingURL = nil;
@@ -283,6 +291,39 @@ - (void)cancel {
[self dialogDidCancel:nil];
}
+- (BOOL)testBoolUrlParam:(NSURL *)url param:(NSString *)param {
+ NSString *paramVal = [self getStringFromUrl: [url absoluteString]
+ needle: param];
+ return [paramVal boolValue];
+}
+
+- (void)dialogSuccessHandleFrictionlessResponses:(NSURL *)url {
+ // did we receive a recipient list?
+ NSString *recipientJson = [self getStringFromUrl:[url absoluteString]
+ needle:@"frictionless_recipients="];
+ if (recipientJson) {
+ // if value parses as an array, treat as set of fbids
+ SBJsonParser *parser = [[[SBJsonParser alloc]
+ init]
+ autorelease];
+ id recipients = [parser objectWithString:recipientJson];
+
+ // if we got something usable, copy the ids out and update the cache
+ if ([recipients isKindOfClass:[NSArray class]]) {
+ NSMutableArray *ids = [[[NSMutableArray alloc]
+ initWithCapacity:[recipients count]]
+ autorelease];
+ for (id recipient in recipients) {
+ NSString *fbid = [NSString stringWithFormat:@"%@", recipient];
+ [ids addObject:fbid];
+ }
+ // we may be tempted to terminate outstanding requests before this
+ // point, but that would cause problems if the user cancelled a dialog
+ [_frictionlessSettings updateRecipientCacheWithRecipients:ids];
+ }
+ }
+}
+
///////////////////////////////////////////////////////////////////////////////////////////////////
// NSObject
@@ -344,6 +385,7 @@ - (void)dealloc {
[_closeButton release];
[_loadingURL release];
[_modalBackgroundView release];
+ [_frictionlessSettings release];
[super dealloc];
}
@@ -360,6 +402,41 @@ - (void)drawRect:(CGRect)rect {
[self strokeLines:webRect stroke:kBorderBlack];
}
+// Display the dialog's WebView with a slick pop-up animation
+- (void)showWebView {
+ UIWindow* window = [UIApplication sharedApplication].keyWindow;
+ if (!window) {
+ window = [[UIApplication sharedApplication].windows objectAtIndex:0];
+ }
+ _modalBackgroundView.frame = window.frame;
+ [_modalBackgroundView addSubview:self];
+ [window addSubview:_modalBackgroundView];
+
+ self.transform = CGAffineTransformScale([self transformForOrientation], 0.001, 0.001);
+ [UIView beginAnimations:nil context:nil];
+ [UIView setAnimationDuration:kTransitionDuration/1.5];
+ [UIView setAnimationDelegate:self];
+ [UIView setAnimationDidStopSelector:@selector(bounce1AnimationStopped)];
+ self.transform = CGAffineTransformScale([self transformForOrientation], 1.1, 1.1);
+ [UIView commitAnimations];
+
+ [self dialogWillAppear];
+ [self addObservers];
+}
+
+// Show a spinner during the loading time for the dialog. This is designed to show
+// on top of the webview but before the contents have loaded.
+- (void)showSpinner {
+ [_spinner sizeToFit];
+ [_spinner startAnimating];
+ _spinner.center = _webView.center;
+}
+
+- (void)hideSpinner {
+ [_spinner stopAnimating];
+ _spinner.hidden = YES;
+}
+
///////////////////////////////////////////////////////////////////////////////////////////////////
// UIWebViewDelegate
@@ -381,6 +458,9 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)
[self dialogDidCancel:url];
}
} else {
+ if (_frictionlessSettings.enabled) {
+ [self dialogSuccessHandleFrictionlessResponses:url];
+ }
[self dialogDidSucceed:url];
}
return NO;
@@ -401,9 +481,15 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
- [_spinner stopAnimating];
- _spinner.hidden = YES;
-
+ if (_isViewInvisible) {
+ // if our cache asks us to hide the view, then we do, but
+ // in case of a stale cache, we will display the view in a moment
+ // note that showing the view now would cause a visible white
+ // flash in the common case where the cache is up to date
+ [self performSelector:@selector(showWebView) withObject:nil afterDelay:.05];
+ } else {
+ [self hideSpinner];
+ }
[self updateWebOrientation];
}
@@ -475,25 +561,35 @@ - (NSString *) getStringFromUrl: (NSString*) url needle:(NSString *) needle {
NSString * str = nil;
NSRange start = [url rangeOfString:needle];
if (start.location != NSNotFound) {
- NSRange end = [[url substringFromIndex:start.location+start.length] rangeOfString:@"&"];
- NSUInteger offset = start.location+start.length;
- str = end.location == NSNotFound
- ? [url substringFromIndex:offset]
- : [url substringWithRange:NSMakeRange(offset, end.location)];
- str = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
- }
-
+ // confirm that the parameter is not a partial name match
+ unichar c = '?';
+ if (start.location != 0) {
+ c = [url characterAtIndex:start.location - 1];
+ }
+ if (c == '?' || c == '&') {
+ NSRange end = [[url substringFromIndex:start.location+start.length] rangeOfString:@"&"];
+ NSUInteger offset = start.location+start.length;
+ str = end.location == NSNotFound ?
+ [url substringFromIndex:offset] :
+ [url substringWithRange:NSMakeRange(offset, end.location)];
+ str = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ }
+ }
return str;
}
- (id)initWithURL: (NSString *) serverURL
params: (NSMutableDictionary *) params
+ isViewInvisible: (BOOL)isViewInvisible
+ frictionlessSettings: (FBFrictionlessRequestSettings*) frictionlessSettings
delegate: (id <FBDialogDelegate>) delegate {
self = [self init];
_serverURL = [serverURL retain];
- _params = [params retain];
+ _params = [params retain];
_delegate = delegate;
+ _isViewInvisible = isViewInvisible;
+ _frictionlessSettings = [frictionlessSettings retain];
return self;
}
@@ -503,7 +599,7 @@ - (void)load {
}
- (void)loadURL:(NSString*)url get:(NSDictionary*)getParams {
-
+
[_loadingURL release];
_loadingURL = [[self generateURL:url params:getParams] retain];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:_loadingURL];
@@ -530,32 +626,10 @@ - (void)show {
innerWidth,
self.frame.size.height - (1 + kBorderWidth*2));
- [_spinner sizeToFit];
- [_spinner startAnimating];
- _spinner.center = _webView.center;
-
- UIWindow* window = [UIApplication sharedApplication].keyWindow;
- if (!window) {
- window = [[UIApplication sharedApplication].windows objectAtIndex:0];
+ if (!_isViewInvisible) {
+ [self showSpinner];
+ [self showWebView];
}
-
- _modalBackgroundView.frame = window.frame;
- [_modalBackgroundView addSubview:self];
- [window addSubview:_modalBackgroundView];
-
- [window addSubview:self];
-
- [self dialogWillAppear];
-
- self.transform = CGAffineTransformScale([self transformForOrientation], 0.001, 0.001);
- [UIView beginAnimations:nil context:nil];
- [UIView setAnimationDuration:kTransitionDuration/1.5];
- [UIView setAnimationDelegate:self];
- [UIView setAnimationDidStopSelector:@selector(bounce1AnimationStopped)];
- self.transform = CGAffineTransformScale([self transformForOrientation], 1.1, 1.1);
- [UIView commitAnimations];
-
- [self addObservers];
}
- (void)dismissWithSuccess:(BOOL)success animated:(BOOL)animated {
@@ -601,4 +675,4 @@ - (void)dialogDidCancel:(NSURL *)url {
[self dismissWithSuccess:NO animated:YES];
}
-@end
+@end
View
74 src/FBFrictionlessRequestSettings.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 Facebook
+ *
+ * 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>
+
+@class Facebook;
+
+/**
+ * Do not use this interface directly, instead, use methods in Facebook.h
+ *
+ * Handles frictionless interaction and recipient-caching by the SDK,
+ * see https://developers.facebook.com/docs/reference/dialogs/requests/
+ */
+@interface FBFrictionlessRequestSettings : NSObject<FBRequestDelegate> {
+@private
+ NSArray* _allowedRecipients;
+ FBRequest* _activeRequest;
+ BOOL _enabled;
+}
+
+/**
+ * BOOL indicating whether frictionless request sending has been enabled
+ */
+@property(nonatomic, readonly) BOOL enabled;
+
+/**
+ * Enable frictionless request sending by the sdk; this means:
+ * 1. query and cache the current set of frictionless recipients
+ * 2. flag other facets of the sdk to behave in a frictionless way
+ */
+- (void)enableWithFacebook:(Facebook*)facebook;
+
+/**
+ * Reload recipient cache; called by the sdk to keep the cache fresh;
+ * method makes graph request: me/apprequestformerrecipients
+ */
+- (void)reloadRecipientCacheWithFacebook:(Facebook*)facebook;
+
+/**
+ * Update the recipient cache; called by the sdk to keep the cache fresh;
+ */
+- (void)updateRecipientCacheWithRecipients:(NSArray*)ids;
+
+/**
+ * Given an fbID for a user, indicates whether user is enabled for
+ * frictionless calls
+ */
+- (BOOL)isFrictionlessEnabledForRecipient:(id)fbid;
+
+/**
+ * Given an array of user fbIDs, indicates whether they are enabled for
+ * frictionless calls
+ */
+- (BOOL)isFrictionlessEnabledForRecipients:(NSArray*)fbids;
+
+/**
+ * init the frictionless cache object
+ */
+- (id)init;
+
+@end
View
162 src/FBFrictionlessRequestSettings.m
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2012 Facebook
+ *
+ * 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 "Facebook.h"
+#import "FBFrictionlessRequestSettings.h"
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// private interface
+//
+@interface FBFrictionlessRequestSettings ()
+
+@property (readwrite, retain) NSArray * allowedRecipients;
+@property (readwrite, retain) FBRequest* activeRequest;
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation FBFrictionlessRequestSettings
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// public
+
+@synthesize enabled = _enabled;
+
+- (id)init {
+ if (self = [super init]) {
+ // start life with an empty frictionless cache
+ self.allowedRecipients = [[[NSArray alloc] init] autorelease];
+ }
+ return self;
+}
+
+- (void)enableWithFacebook:(Facebook*)facebook {
+ if (!_enabled) {
+ _enabled = true;
+ [self reloadRecipientCacheWithFacebook:facebook];
+ }
+}
+
+- (void)reloadRecipientCacheWithFacebook:(Facebook *)facebook {
+ // request the list of frictionless recipients from the server
+ id request = [facebook requestWithGraphPath:@"me/apprequestformerrecipients"
+ andDelegate:self];
+ if (request) {
+ self.activeRequest = request;
+ }
+}
+
+- (void)updateRecipientCacheWithRecipients:(NSArray*)ids {
+ // if setting recipients directly, no need to complete pending request
+ self.activeRequest = nil;
+
+ if (ids == nil) {
+ self.allowedRecipients = [[[NSArray alloc] init] autorelease];
+ } else {
+ self.allowedRecipients = [[[NSArray alloc] initWithArray:ids] autorelease];
+ }
+}
+
+- (BOOL)isFrictionlessEnabledForRecipient:(NSString *)fbid {
+ // trim whitespace from edges
+ fbid = [fbid stringByTrimmingCharactersInSet:
+ [NSCharacterSet whitespaceCharacterSet]];
+
+ // linear search through cache for a match
+ for (NSString *entry in self.allowedRecipients) {
+ if ([entry isEqualToString:fbid]) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+- (BOOL)isFrictionlessEnabledForRecipients:(NSArray*)fbids {
+ // we handle arrays of NSString and NSNumber, and throw on anything else
+ for (id fbid in fbids) {
+ NSString* fbidstr;
+ // give us a number, and we convert it to a string
+ if ([fbid isKindOfClass:[NSNumber class]]) {
+ fbidstr = [(NSNumber*)fbid stringValue];
+ } else if ([fbid isKindOfClass:[NSString class]]) {
+ // or give us a string, and we just use it as is
+ fbidstr = (NSString*)fbid;
+ } else {
+ // unexpected type found in the array of fbids
+ @throw [NSException exceptionWithName:NSInvalidArgumentException
+ reason:@"items in fbids must be NSString or NSNumber"
+ userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
+ [fbid class], @"invalid class",
+ nil]];
+ }
+
+ // if we miss our cache once, we fail the set
+ if (![self isFrictionlessEnabledForRecipient:fbidstr]) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// FBRequestDelegate
+
+- (void)request:(FBRequest *)request
+ didLoad:(id)result {
+
+ // a little request bookkeeping
+ self.activeRequest = nil;
+
+ int items = [[result objectForKey: @"data"] count];
+ NSMutableArray* recipients = [[[NSMutableArray alloc] initWithCapacity: items] autorelease];
+
+ for (int i = 0; i < items; i++) {
+ [recipients addObject: [[[result objectForKey: @"data"]
+ objectAtIndex: i]
+ objectForKey: @"recipient_id"]] ;
+ }
+
+ self.allowedRecipients = recipients;
+}
+
+- (void)request:(FBRequest *)request didFailWithError:(NSError *)error {
+ // if the request to load the frictionless recipients fails, proceed without updating
+ // the recipients cache; the cache may become invalid due to a failed update or other reasons
+ // (e.g. simultaneous use of the same app from multiple devices), in the case of an invalid
+ // cache, a request dialog may either appear a moment later than it usually would, or appear
+ // briefly when it should not appear at all; in either case the correct request behavior
+ // occurs, and the cache is updated
+ self.activeRequest = nil;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// NSObject
+
+- (void)dealloc {
+ self.activeRequest = nil;
+ self.allowedRecipients = nil;
+ [super dealloc];
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// private helpers
+//
+
+@synthesize allowedRecipients = _allowedRecipients;
+@synthesize activeRequest = _activeRequest;
+
+@end
View
6 src/FBLoginDialog.h
@@ -6,7 +6,7 @@
* 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.
@@ -27,12 +27,12 @@
*/
@interface FBLoginDialog : FBDialog {
- id<FBLoginDialogDelegate> _loginDelegate;
+ id<FBLoginDialogDelegate> _loginDelegate;
}
-(id) initWithURL:(NSString *) loginURL
loginParams:(NSMutableDictionary *) params
- delegate:(id <FBLoginDialogDelegate>) delegate;
+ delegate:(id <FBLoginDialogDelegate>) delegate;
@end
///////////////////////////////////////////////////////////////////////////////////////////////////
View
76 src/FBLoginDialog.m
@@ -30,12 +30,12 @@ @implementation FBLoginDialog
- (id)initWithURL:(NSString*) loginURL
loginParams:(NSMutableDictionary*) params
delegate:(id <FBLoginDialogDelegate>) delegate{
-
- self = [super init];
- _serverURL = [loginURL retain];
- _params = [params retain];
- _loginDelegate = delegate;
- return self;
+
+ self = [super init];
+ _serverURL = [loginURL retain];
+ _params = [params retain];
+ _loginDelegate = delegate;
+ return self;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -45,50 +45,50 @@ - (id)initWithURL:(NSString*) loginURL
* Override FBDialog : to call when the webView Dialog did succeed
*/
- (void) dialogDidSucceed:(NSURL*)url {
- NSString *q = [url absoluteString];
- NSString *token = [self getStringFromUrl:q needle:@"access_token="];
- NSString *expTime = [self getStringFromUrl:q needle:@"expires_in="];
- NSDate *expirationDate =nil;
-
- if (expTime != nil) {
- int expVal = [expTime intValue];
- if (expVal == 0) {
- expirationDate = [NSDate distantFuture];
- } else {
- expirationDate = [NSDate dateWithTimeIntervalSinceNow:expVal];
+ NSString *q = [url absoluteString];
+ NSString *token = [self getStringFromUrl:q needle:@"access_token="];
+ NSString *expTime = [self getStringFromUrl:q needle:@"expires_in="];
+ NSDate *expirationDate =nil;
+
+ if (expTime != nil) {
+ int expVal = [expTime intValue];
+ if (expVal == 0) {
+ expirationDate = [NSDate distantFuture];
+ } else {
+ expirationDate = [NSDate dateWithTimeIntervalSinceNow:expVal];
+ }
}
- }
-
- if ((token == (NSString *) [NSNull null]) || (token.length == 0)) {
- [self dialogDidCancel:url];
- [self dismissWithSuccess:NO animated:YES];
- } else {
- if ([_loginDelegate respondsToSelector:@selector(fbDialogLogin:expirationDate:)]) {
- [_loginDelegate fbDialogLogin:token expirationDate:expirationDate];
+
+ if ((token == (NSString *) [NSNull null]) || (token.length == 0)) {
+ [self dialogDidCancel:url];
+ [self dismissWithSuccess:NO animated:YES];
+ } else {
+ if ([_loginDelegate respondsToSelector:@selector(fbDialogLogin:expirationDate:)]) {
+ [_loginDelegate fbDialogLogin:token expirationDate:expirationDate];
+ }
+ [self dismissWithSuccess:YES animated:YES];
}
- [self dismissWithSuccess:YES animated:YES];
- }
-
+
}
/**
* Override FBDialog : to call with the login dialog get canceled
*/
- (void)dialogDidCancel:(NSURL *)url {
- [self dismissWithSuccess:NO animated:YES];
- if ([_loginDelegate respondsToSelector:@selector(fbDialogNotLogin:)]) {
- [_loginDelegate fbDialogNotLogin:YES];
- }
+ [self dismissWithSuccess:NO animated:YES];
+ if ([_loginDelegate respondsToSelector:@selector(fbDialogNotLogin:)]) {
+ [_loginDelegate fbDialogNotLogin:YES];
+ }
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
- if (!(([error.domain isEqualToString:@"NSURLErrorDomain"] && error.code == -999) ||
- ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102))) {
- [super webView:webView didFailLoadWithError:error];
- if ([_loginDelegate respondsToSelector:@selector(fbDialogNotLogin:)]) {
- [_loginDelegate fbDialogNotLogin:NO];
+ if (!(([error.domain isEqualToString:@"NSURLErrorDomain"] && error.code == -999) ||
+ ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102))) {
+ [super webView:webView didFailLoadWithError:error];
+ if ([_loginDelegate respondsToSelector:@selector(fbDialogNotLogin:)]) {
+ [_loginDelegate fbDialogNotLogin:NO];
+ }
}
- }
}
@end
View
28 src/FBRequest.h
@@ -6,7 +6,7 @@
* 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.
@@ -20,10 +20,10 @@
@protocol FBRequestDelegate;
enum {
- kFBRequestStateReady,
- kFBRequestStateLoading,
- kFBRequestStateComplete,
- kFBRequestStateError
+ kFBRequestStateReady,
+ kFBRequestStateLoading,
+ kFBRequestStateComplete,
+ kFBRequestStateError
};
typedef NSUInteger FBRequestState;
@@ -31,15 +31,15 @@ typedef NSUInteger FBRequestState;
* Do not use this interface directly, instead, use method in Facebook.h
*/
@interface FBRequest : NSObject {
- id<FBRequestDelegate> _delegate;
- NSString* _url;
- NSString* _httpMethod;
- NSMutableDictionary* _params;
- NSURLConnection* _connection;
- NSMutableData* _responseText;
- FBRequestState _state;
- NSError* _error;
- BOOL _sessionDidExpire;
+ id<FBRequestDelegate> _delegate;
+ NSString* _url;
+ NSString* _httpMethod;
+ NSMutableDictionary* _params;
+ NSURLConnection* _connection;
+ NSMutableData* _responseText;
+ FBRequestState _state;
+ NSError* _error;
+ BOOL _sessionDidExpire;
}
View
423 src/FBRequest.m
@@ -6,7 +6,7 @@
* 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.
@@ -45,6 +45,7 @@ @implementation FBRequest
state = _state,
sessionDidExpire = _sessionDidExpire,
error = _error;
+
//////////////////////////////////////////////////////////////////////////////////////////////////
// class public
@@ -52,24 +53,24 @@ + (FBRequest *)getRequestWithParams:(NSMutableDictionary *) params
httpMethod:(NSString *) httpMethod
delegate:(id<FBRequestDelegate>) delegate
requestURL:(NSString *) url {
-
- FBRequest* request = [[[FBRequest alloc] init] autorelease];
- request.delegate = delegate;
- request.url = url;
- request.httpMethod = httpMethod;
- request.params = params;
- request.connection = nil;
- request.responseText = nil;
-
- return request;
+
+ FBRequest* request = [[[FBRequest alloc] init] autorelease];
+ request.delegate = delegate;
+ request.url = url;
+ request.httpMethod = httpMethod;
+ request.params = params;
+ request.connection = nil;
+ request.responseText = nil;
+
+ return request;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// private
+ (NSString *)serializeURL:(NSString *)baseUrl
- params:(NSDictionary *)params {
- return [self serializeURL:baseUrl params:params httpMethod:@"GET"];
+ params:(NSDictionary *)params {
+ return [self serializeURL:baseUrl params:params httpMethod:@"GET"];
}
/**
@@ -78,163 +79,163 @@ + (NSString *)serializeURL:(NSString *)baseUrl
+ (NSString*)serializeURL:(NSString *)baseUrl
params:(NSDictionary *)params
httpMethod:(NSString *)httpMethod {
-
- NSURL* parsedURL = [NSURL URLWithString:baseUrl];
- NSString* queryPrefix = parsedURL.query ? @"&" : @"?";
-
- NSMutableArray* pairs = [NSMutableArray array];
- for (NSString* key in [params keyEnumerator]) {
- if (([[params valueForKey:key] isKindOfClass:[UIImage class]])
- ||([[params valueForKey:key] isKindOfClass:[NSData class]])) {
- if ([httpMethod isEqualToString:@"GET"]) {
- NSLog(@"can not use GET to upload a file");
- }
- continue;
+
+ NSURL* parsedURL = [NSURL URLWithString:baseUrl];
+ NSString* queryPrefix = parsedURL.query ? @"&" : @"?";
+
+ NSMutableArray* pairs = [NSMutableArray array];
+ for (NSString* key in [params keyEnumerator]) {
+ if (([[params valueForKey:key] isKindOfClass:[UIImage class]])
+ ||([[params valueForKey:key] isKindOfClass:[NSData class]])) {
+ if ([httpMethod isEqualToString:@"GET"]) {
+ NSLog(@"can not use GET to upload a file");
+ }
+ continue;
+ }
+
+ NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(
+ NULL, /* allocator */
+ (CFStringRef)[params objectForKey:key],
+ NULL, /* charactersToLeaveUnescaped */
+ (CFStringRef)@"!*'();:@&=+$,/?%#[]",
+ kCFStringEncodingUTF8);
+
+ [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, escaped_value]];
+ [escaped_value release];
}
-
- NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(
- NULL, /* allocator */
- (CFStringRef)[params objectForKey:key],
- NULL, /* charactersToLeaveUnescaped */
- (CFStringRef)@"!*'();:@&=+$,/?%#[]",
- kCFStringEncodingUTF8);
-
- [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, escaped_value]];
- [escaped_value release];
- }
- NSString* query = [pairs componentsJoinedByString:@"&"];
-
- return [NSString stringWithFormat:@"%@%@%@", baseUrl, queryPrefix, query];
+ NSString* query = [pairs componentsJoinedByString:@"&"];
+
+ return [NSString stringWithFormat:@"%@%@%@", baseUrl, queryPrefix, query];
}
/**
* Body append for POST method
*/
- (void)utfAppendBody:(NSMutableData *)body data:(NSString *)data {
- [body appendData:[data dataUsingEncoding:NSUTF8StringEncoding]];
+ [body appendData:[data dataUsingEncoding:NSUTF8StringEncoding]];
}
/**
* Generate body for POST method
*/
- (NSMutableData *)generatePostBody {
- NSMutableData *body = [NSMutableData data];
- NSString *endLine = [NSString stringWithFormat:@"\r\n--%@\r\n", kStringBoundary];
- NSMutableDictionary *dataDictionary = [NSMutableDictionary dictionary];
-
- [self utfAppendBody:body data:[NSString stringWithFormat:@"--%@\r\n", kStringBoundary]];
-
- for (id key in [_params keyEnumerator]) {
-
- if (([[_params valueForKey:key] isKindOfClass:[UIImage class]])
- ||([[_params valueForKey:key] isKindOfClass:[NSData class]])) {
-
- [dataDictionary setObject:[_params valueForKey:key] forKey:key];
- continue;
-
- }
-
- [self utfAppendBody:body
- data:[NSString
- stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",
- key]];
- [self utfAppendBody:body data:[_params valueForKey:key]];
-
- [self utfAppendBody:body data:endLine];
- }
-
- if ([dataDictionary count] > 0) {
- for (id key in dataDictionary) {
- NSObject *dataParam = [dataDictionary valueForKey:key];
- if ([dataParam isKindOfClass:[UIImage class]]) {
- NSData* imageData = UIImagePNGRepresentation((UIImage*)dataParam);
- [self utfAppendBody:body
- data:[NSString stringWithFormat:
- @"Content-Disposition: form-data; filename=\"%@\"\r\n", key]];
- [self utfAppendBody:body
- data:[NSString stringWithString:@"Content-Type: image/png\r\n\r\n"]];
- [body appendData:imageData];
- } else {
- NSAssert([dataParam isKindOfClass:[NSData class]],
- @"dataParam must be a UIImage or NSData");
- [self utfAppendBody:body
- data:[NSString stringWithFormat:
- @"Content-Disposition: form-data; filename=\"%@\"\r\n", key]];
+ NSMutableData *body = [NSMutableData data];
+ NSString *endLine = [NSString stringWithFormat:@"\r\n--%@\r\n", kStringBoundary];
+ NSMutableDictionary *dataDictionary = [NSMutableDictionary dictionary];
+
+ [self utfAppendBody:body data:[NSString stringWithFormat:@"--%@\r\n", kStringBoundary]];
+
+ for (id key in [_params keyEnumerator]) {
+
+ if (([[_params valueForKey:key] isKindOfClass:[UIImage class]])
+ ||([[_params valueForKey:key] isKindOfClass:[NSData class]])) {
+
+ [dataDictionary setObject:[_params valueForKey:key] forKey:key];
+ continue;
+
+ }
+
[self utfAppendBody:body
- data:[NSString stringWithString:@"Content-Type: content/unknown\r\n\r\n"]];
- [body appendData:(NSData*)dataParam];
- }
- [self utfAppendBody:body data:endLine];
-
+ data:[NSString
+ stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",
+ key]];
+ [self utfAppendBody:body data:[_params valueForKey:key]];
+
+ [self utfAppendBody:body data:endLine];
}
- }
-
- return body;
+
+ if ([dataDictionary count] > 0) {
+ for (id key in dataDictionary) {
+ NSObject *dataParam = [dataDictionary valueForKey:key];
+ if ([dataParam isKindOfClass:[UIImage class]]) {
+ NSData* imageData = UIImagePNGRepresentation((UIImage*)dataParam);
+ [self utfAppendBody:body
+ data:[NSString stringWithFormat:
+ @"Content-Disposition: form-data; filename=\"%@\"\r\n", key]];
+ [self utfAppendBody:body
+ data:[NSString stringWithString:@"Content-Type: image/png\r\n\r\n"]];
+ [body appendData:imageData];
+ } else {
+ NSAssert([dataParam isKindOfClass:[NSData class]],
+ @"dataParam must be a UIImage or NSData");
+ [self utfAppendBody:body
+ data:[NSString stringWithFormat:
+ @"Content-Disposition: form-data; filename=\"%@\"\r\n", key]];
+ [self utfAppendBody:body
+ data:[NSString stringWithString:@"Content-Type: content/unknown\r\n\r\n"]];
+ [body appendData:(NSData*)dataParam];
+ }
+ [self utfAppendBody:body data:endLine];
+
+ }
+ }
+
+ return body;
}
/**
* Formulate the NSError
*/
- (id)formError:(NSInteger)code userInfo:(NSDictionary *) errorData {
- return [NSError errorWithDomain:@"facebookErrDomain" code:code userInfo:errorData];
-
+ return [NSError errorWithDomain:@"facebookErrDomain" code:code userInfo:errorData];
+
}
/**
* parse the response data
*/
- (id)parseJsonResponse:(NSData *)data error:(NSError **)error {
-
- NSString* responseString = [[[NSString alloc] initWithData:data
- encoding:NSUTF8StringEncoding]
- autorelease];
- SBJSON *jsonParser = [[SBJSON new] autorelease];
- if ([responseString isEqualToString:@"true"]) {
- return [NSDictionary dictionaryWithObject:@"true" forKey:@"result"];
- } else if ([responseString isEqualToString:@"false"]) {
- if (error != nil) {
- *error = [self formError:kGeneralErrorCode
- userInfo:[NSDictionary
- dictionaryWithObject:@"This operation can not be completed"
- forKey:@"error_msg"]];
- }
- return nil;
- }
-
-
- id result = [jsonParser objectWithString:responseString];
-
- if (![result isKindOfClass:[NSArray class]]) {
- if ([result objectForKey:@"error"] != nil) {
- if (error != nil) {
- *error = [self formError:kGeneralErrorCode
- userInfo:result];
- }
- return nil;
- }
-
- if ([result objectForKey:@"error_code"] != nil) {
- if (error != nil) {
- *error = [self formError:[[result objectForKey:@"error_code"] intValue] userInfo:result];
- }
- return nil;
- }
-
- if ([result objectForKey:@"error_msg"] != nil) {
- if (error != nil) {
- *error = [self formError:kGeneralErrorCode userInfo:result];
- }
+
+ NSString* responseString = [[[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding]
+ autorelease];
+ SBJSON *jsonParser = [[SBJSON new] autorelease];
+ if ([responseString isEqualToString:@"true"]) {
+ return [NSDictionary dictionaryWithObject:@"true" forKey:@"result"];
+ } else if ([responseString isEqualToString:@"false"]) {
+ if (error != nil) {
+ *error = [self formError:kGeneralErrorCode
+ userInfo:[NSDictionary
+ dictionaryWithObject:@"This operation can not be completed"
+ forKey:@"error_msg"]];
+ }
+ return nil;
}
-
- if ([result objectForKey:@"error_reason"] != nil) {
- if (error != nil) {
- *error = [self formError:kGeneralErrorCode userInfo:result];
- }
+
+
+ id result = [jsonParser objectWithString:responseString];
+
+ if (![result isKindOfClass:[NSArray class]]) {
+ if ([result objectForKey:@"error"] != nil) {
+ if (error != nil) {
+ *error = [self formError:kGeneralErrorCode
+ userInfo:result];
+ }
+ return nil;
+ }
+
+ if ([result objectForKey:@"error_code"] != nil) {
+ if (error != nil) {
+ *error = [self formError:[[result objectForKey:@"error_code"] intValue] userInfo:result];
+ }
+ return nil;
+ }
+
+ if ([result objectForKey:@"error_msg"] != nil) {
+ if (error != nil) {
+ *error = [self formError:kGeneralErrorCode userInfo:result];
+ }
+ }
+
+ if ([result objectForKey:@"error_reason"] != nil) {
+ if (error != nil) {
+ *error = [self formError:kGeneralErrorCode userInfo:result];
+ }
+ }
}
- }
-
- return result;
-
+
+ return result;
+
}
/*
@@ -242,40 +243,40 @@ - (id)parseJsonResponse:(NSData *)data error:(NSError **)error {
* fails with error
*/
- (void)failWithError:(NSError *)error {
- if ([error code] == kRESTAPIAccessTokenErrorCode) {
- self.sessionDidExpire = YES;
- }
- if ([_delegate respondsToSelector:@selector(request:didFailWithError:)]) {
- [_delegate request:self didFailWithError:error];
- }
+ if ([error code] == kRESTAPIAccessTokenErrorCode) {
+ self.sessionDidExpire = YES;
+ }
+ if ([_delegate respondsToSelector:@selector(request:didFailWithError:)]) {
+ [_delegate request:self didFailWithError:error];
+ }
}
/*
* private helper function: handle the response data
*/
- (void)handleResponseData:(NSData *)data {
- if ([_delegate respondsToSelector:
- @selector(request:didLoadRawResponse:)]) {
- [_delegate request:self didLoadRawResponse:data];
- }
-
- NSError* error = nil;
- id result = [self parseJsonResponse:data error:&error];
- self.error = error;
-
- if ([_delegate respondsToSelector:@selector(request:didLoad:)] ||
- [_delegate respondsToSelector:
- @selector(request:didFailWithError:)]) {
-
- if (error) {
- [self failWithError:error];
- } else if ([_delegate respondsToSelector:
- @selector(request:didLoad:)]) {
- [_delegate request:self didLoad:(result == nil ? data : result)];
+ if ([_delegate respondsToSelector:
+ @selector(request:didLoadRawResponse:)]) {
+ [_delegate request:self didLoadRawResponse:data];
}
-
- }
-
+
+ NSError* error = nil;
+ id result = [self parseJsonResponse:data error:&error];
+ self.error = error;
+
+ if ([_delegate respondsToSelector:@selector(request:didLoad:)] ||
+ [_delegate respondsToSelector:
+ @selector(request:didFailWithError:)]) {
+
+ if (error) {
+ [self failWithError:error];
+ } else if ([_delegate respondsToSelector:
+ @selector(request:didLoad:)]) {
+ [_delegate request:self didLoad:(result == nil ? data : result)];
+ }
+
+ }
+
}
@@ -287,89 +288,89 @@ - (void)handleResponseData:(NSData *)data {
* @return boolean - whether this request is processing
*/
- (BOOL)loading {
- return !!_connection;
+ return !!_connection;
}
/**
* make the Facebook request
*/
- (void)connect {
-
- if ([_delegate respondsToSelector:@selector(requestLoading:)]) {
- [_delegate requestLoading:self];
- }
-
- NSString* url = [[self class] serializeURL:_url params:_params httpMethod:_httpMethod];
- NSMutableURLRequest* request =
+
+ if ([_delegate respondsToSelector:@selector(requestLoading:)]) {
+ [_delegate requestLoading:self];
+ }
+
+ NSString* url = [[self class] serializeURL:_url params:_params httpMethod:_httpMethod];
+ NSMutableURLRequest* request =
[NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:kTimeoutInterval];
- [request setValue:kUserAgent forHTTPHeaderField:@"User-Agent"];
+ [request setValue:kUserAgent forHTTPHeaderField:@"User-Agent"];
- [request setHTTPMethod:self.httpMethod];
- if ([self.httpMethod isEqualToString: @"POST"]) {
- NSString* contentType = [NSString
+ [request setHTTPMethod:self.httpMethod];
+ if ([self.httpMethod isEqualToString: @"POST"]) {
+ NSString* contentType = [NSString
stringWithFormat:@"multipart/form-data; boundary=%@", kStringBoundary];
- [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
+ [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
- [request setHTTPBody:[self generatePostBody]];
- }
+ [request setHTTPBody:[self generatePostBody]];
+ }
- _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
- self.state = kFBRequestStateLoading;
- self.sessionDidExpire = NO;
+ _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
+ self.state = kFBRequestStateLoading;
+ self.sessionDidExpire = NO;
}
/**
* Free internal structure
*/
- (void)dealloc {
- [_connection cancel];
- [_connection release];
- [_responseText release];
- [_url release];
- [_httpMethod release];
- [_params release];
- [super dealloc];
+ [_connection cancel];
+ [_connection release];
+ [_responseText release];
+ [_url release];
+ [_httpMethod release];
+ [_params release];
+ [super dealloc];
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
- _responseText = [[NSMutableData alloc] init];
-
- NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
- if ([_delegate respondsToSelector:
- @selector(request:didReceiveResponse:)]) {
- [_delegate request:self didReceiveResponse:httpResponse];
- }
+ _responseText = [[NSMutableData alloc] init];
+
+ NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
+ if ([_delegate respondsToSelector:
+ @selector(request:didReceiveResponse:)]) {
+ [_delegate request:self didReceiveResponse:httpResponse];
+ }
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
- [_responseText appendData:data];
+ [_responseText appendData:data];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
- willCacheResponse:(NSCachedURLResponse*)cachedResponse {
- return nil;
+ willCacheResponse:(NSCachedURLResponse*)cachedResponse {
+ return nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
- [self handleResponseData:_responseText];
+ [self handleResponseData:_responseText];
- self.responseText = nil;
- self.connection = nil;
- self.state = kFBRequestStateComplete;
+ self.responseText = nil;
+ self.connection = nil;
+ self.state = kFBRequestStateComplete;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
- [self failWithError:error];
+ [self failWithError:error];
- self.responseText = nil;
- self.connection = nil;
- self.state = kFBRequestStateComplete;
+ self.responseText = nil;
+ self.connection = nil;
+ self.state = kFBRequestStateComplete;
}
@end
View
34 src/Facebook.h
@@ -16,6 +16,7 @@
#import "FBLoginDialog.h"
#import "FBRequest.h"
+#import "FBFrictionlessRequestSettings.h"
@protocol FBSessionDelegate;
@@ -26,23 +27,26 @@
* pop-ups promoting for credentials, permissions, stream posts, etc.)
*/
@interface Facebook : NSObject<FBLoginDialogDelegate,FBRequestDelegate>{
- NSString* _accessToken;
- NSDate* _expirationDate;
- id<FBSessionDelegate> _sessionDelegate;
- NSMutableSet* _requests;
- FBDialog* _loginDialog;
- FBDialog* _fbDialog;
- NSString* _appId;
- NSString* _urlSchemeSuffix;
- NSArray* _permissions;
- BOOL _isExtendingAccessToken;
- NSDate* _lastAccessTokenUpdate;
+ NSString* _accessToken;
+ NSDate* _expirationDate;
+ id<FBSessionDelegate> _sessionDelegate;
+ NSMutableSet* _requests;
+ FBDialog* _loginDialog;
+ FBDialog* _fbDialog;
+ NSString* _appId;
+ NSString* _urlSchemeSuffix;
+ NSArray* _permissions;
+ BOOL _isExtendingAccessToken;
+ NSDate* _lastAccessTokenUpdate;
+ FBFrictionlessRequestSettings* _frictionlessRequestSettings;
}
@property(nonatomic, copy) NSString* accessToken;
@property(nonatomic, copy) NSDate* expirationDate;
@property(nonatomic, assign) id<FBSessionDelegate> sessionDelegate;
@property(nonatomic, copy) NSString* urlSchemeSuffix;
+@property(nonatomic, readonly, getter=isFrictionlessRequestsEnabled)
+ BOOL isFrictionlessRequestsEnabled;
- (id)initWithAppId:(NSString *)appId
andDelegate:(id<FBSessionDelegate>)delegate;
@@ -94,6 +98,14 @@
- (BOOL)isSessionValid;
+- (void)enableFrictionlessRequests;
+
+- (void)reloadFrictionlessRecipientCache;
+
+- (BOOL)isFrictionlessEnabledForRecipient:(id)fbid;
+
+- (BOOL)isFrictionlessEnabledForRecipients:(NSArray*)fbids;
+
@end
////////////////////////////////////////////////////////////////////////////////
View
669 src/Facebook.m
@@ -17,6 +17,7 @@
#import "Facebook.h"
#import "FBLoginDialog.h"
#import "FBRequest.h"
+#import "JSON.h"
static NSString* kDialogBaseURL = @"https://m.facebook.com/dialog/";
static NSString* kGraphBaseURL = @"https://graph.facebook.com/";
@@ -27,6 +28,7 @@
static NSString* kRedirectURL = @"fbconnect://success";
static NSString* kLogin = @"oauth";
+static NSString* kApprequests = @"apprequests";
static NSString* kSDK = @"ios";
static NSString* kSDKVersion = @"2";
@@ -51,12 +53,12 @@ @interface Facebook ()
@implementation Facebook
-@synthesize accessToken = _accessToken,
- expirationDate = _expirationDate,
- sessionDelegate = _sessionDelegate,
- permissions = _permissions,
- urlSchemeSuffix = _urlSchemeSuffix,
- appId = _appId;
+@synthesize accessToken = _accessToken,
+ expirationDate = _expirationDate,
+ sessionDelegate = _sessionDelegate,
+ permissions = _permissions,
+ urlSchemeSuffix = _urlSchemeSuffix,
+ appId = _appId;
///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -97,48 +99,53 @@ - (id)initWithAppId:(NSString *)appId
- (id)initWithAppId:(NSString *)appId
urlSchemeSuffix:(NSString *)urlSchemeSuffix
andDelegate:(id<FBSessionDelegate>)delegate {
-
- self = [super init];
- if (self) {
- _requests = [[NSMutableSet alloc] init];
- _lastAccessTokenUpdate = [[NSDate distantPast] retain];
- self.appId = appId;
- self.sessionDelegate = delegate;
- self.urlSchemeSuffix = urlSchemeSuffix;
- }
- return self;
+
+ self = [super init];
+ if (self) {
+ _requests = [[NSMutableSet alloc] init];
+ _lastAccessTokenUpdate = [[NSDate distantPast] retain];
+ _frictionlessRequestSettings = [[FBFrictionlessRequestSettings alloc] init];
+ self.appId = appId;
+ self.sessionDelegate = delegate;
+ self.urlSchemeSuffix = urlSchemeSuffix;
+ }
+ return self;
}
/**
* Override NSObject : free the space
*/
- (void)dealloc {
- for (FBRequest* _request in _requests) {
- [_request removeObserver:self forKeyPath:requestFinishedKeyPath];
- }
- [_lastAccessTokenUpdate release];
- [_accessToken release];
- [_expirationDate release];
- [_requests release];
- [_loginDialog release];
- [_fbDialog release];
- [_appId release];
- [_permissions release];
- [_urlSchemeSuffix release];
- [super dealloc];
+ for (FBRequest* _request in _requests) {
+ [_request removeObserver:self forKeyPath:requestFinishedKeyPath];
+ }
+ [_lastAccessTokenUpdate release];
+ [_accessToken release];
+ [_expirationDate release];
+ [_requests release];
+ [_loginDialog release];
+ [_fbDialog release];
+ [_appId release];
+ [_permissions release];
+ [_urlSchemeSuffix release];
+ [_frictionlessRequestSettings release];
+ [super dealloc];
}
- (void)invalidateSession {
- self.accessToken = nil;
- self.expirationDate = nil;
-
- NSHTTPCookieStorage* cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage];
- NSArray* facebookCookies = [cookies cookiesForURL:
+ self.accessToken = nil;
+ self.expirationDate = nil;
+
+ NSHTTPCookieStorage* cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ NSArray* facebookCookies = [cookies cookiesForURL:
[NSURL URLWithString:@"http://login.facebook.com"]];
-
- for (NSHTTPCookie* cookie in facebookCookies) {
- [cookies deleteCookie:cookie];
- }
+
+ for (NSHTTPCookie* cookie in facebookCookies) {
+ [cookies deleteCookie:cookie];
+ }
+
+ // setting to nil also terminates any active request for whitelist
+ [_frictionlessRequestSettings updateRecipientCacheWithRecipients:nil];
}
/**
@@ -158,52 +165,50 @@ - (FBRequest*)openUrl:(NSString *)url
params:(NSMutableDictionary *)params
httpMethod:(NSString *)httpMethod
delegate:(id<FBRequestDelegate>)delegate {
-
- [params setValue:@"json" forKey:@"format"];
- [params setValue:kSDK forKey:@"sdk"];
- [params setValue:kSDKVersion forKey:@"sdk_version"];
- if ([self isSessionValid]) {
- [params setValue:self.accessToken forKey:@"access_token"];
- }
-
- [self extendAccessTokenIfNeeded];
-
- FBRequest* _request = [FBRequest getRequestWithParams:params
- httpMethod:httpMethod
- delegate:delegate
- requestURL:url];
- [_requests addObject:_request];
- [_request addObserver:self forKeyPath:requestFinishedKeyPath options:0 context:finishedContext];
- [_request connect];
- return _request;
+
+ [params setValue:@"json" forKey:@"format"];
+ [params setValue:kSDK forKey:@"sdk"];
+ [params setValue:kSDKVersion forKey:@"sdk_version"];
+ if ([self isSessionValid]) {
+ [params setValue:self.accessToken forKey:@"access_token"];
+ }
+
+ [self extendAccessTokenIfNeeded];
+
+ FBRequest* _request = [FBRequest getRequestWithParams:params
+ httpMethod:httpMethod
+ delegate:delegate
+ requestURL:url];
+ [_requests addObject:_request];
+ [_request addObserver:self forKeyPath:requestFinishedKeyPath options:0 context:finishedContext];
+ [_request connect];
+ return _request;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
- if (context == finishedContext) {
- FBRequest* _request = (FBRequest*)object;
- FBRequestState requestState = [_request state];
- if (requestState == kFBRequestStateComplete) {
- if ([_request sessionDidExpire]) {
- [self invalidateSession];
- if ([self.sessionDelegate respondsToSelector:@selector(fbSessionInvalidated)]) {
- [self.sessionDelegate fbSessionInvalidated];
+ if (context == finishedContext) {
+ FBRequest* _request = (FBRequest*)object;
+ FBRequestState requestState = [_request state];
+ if (requestState == kFBRequestStateComplete) {
+ if ([_request sessionDidExpire]) {
+ [self invalidateSession];
+ if ([self.sessionDelegate respondsToSelector:@selector(fbSessionInvalidated)]) {
+ [self.sessionDelegate fbSessionInvalidated];
+ }
+ }
+ [_request removeObserver:self forKeyPath:requestFinishedKeyPath];
+ [_requests removeObject:_request];
}
- }
- [_request removeObserver:self forKeyPath:requestFinishedKeyPath];
- [_requests removeObject:_request];
}
- } else {
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
- }
}
/**
* A private function for getting the app's base url.
*/
- (NSString *)getOwnBaseUrl {
- return [NSString stringWithFormat:@"fb%@%@://authorize",
- _appId,
- _urlSchemeSuffix ? _urlSchemeSuffix : @""];
+ return [NSString stringWithFormat:@"fb%@%@://authorize",
+ _appId,
+ _urlSchemeSuffix ? _urlSchemeSuffix : @""];
}
/**
@@ -211,63 +216,63 @@ - (NSString *)getOwnBaseUrl {
*/
- (void)authorizeWithFBAppAuth:(BOOL)tryFBAppAuth
safariAuth:(BOOL)trySafariAuth {
- NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- _appId, @"client_id",
- @"user_agent", @"type",
- kRedirectURL, @"redirect_uri",
- @"touch", @"display",
- kSDK, @"sdk",
- nil];
-
- NSString *loginDialogURL = [kDialogBaseURL stringByAppendingString:kLogin];
-
- if (_permissions != nil) {
- NSString* scope = [_permissions componentsJoinedByString:@","];
- [params setValue:scope forKey:@"scope"];
- }
-
- if (_urlSchemeSuffix) {
- [params setValue:_urlSchemeSuffix forKey:@"local_client_id"];
- }
-
- // If the device is running a version of iOS that supports multitasking,
- // try to obtain the access token from the Facebook app installed
- // on the device.
- // If the Facebook app isn't installed or it doesn't support
- // the fbauth:// URL scheme, fall back on Safari for obtaining the access token.
- // This minimizes the chance that the user will have to enter his or
- // her credentials in order to authorize the application.
- BOOL didOpenOtherApp = NO;
- UIDevice *device = [UIDevice currentDevice];
- if ([device respondsToSelector:@selector(isMultitaskingSupported)] && [device isMultitaskingSupported]) {
- if (tryFBAppAuth) {
- NSString *scheme = kFBAppAuthURLScheme;
- if (_urlSchemeSuffix) {
- scheme = [scheme stringByAppendingString:@"2"];
- }
- NSString *urlPrefix = [NSString stringWithFormat:@"%@://%@", scheme, kFBAppAuthURLPath];
- NSString *fbAppUrl = [FBRequest serializeURL:urlPrefix params:params];
- didOpenOtherApp = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:fbAppUrl]];
+ NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ _appId, @"client_id",
+ @"user_agent", @"type",
+ kRedirectURL, @"redirect_uri",
+ @"touch", @"display",
+ kSDK, @"sdk",
+ nil];
+
+ NSString *loginDialogURL = [kDialogBaseURL stringByAppendingString:kLogin];
+
+ if (_permissions != nil) {
+ NSString* scope = [_permissions componentsJoinedByString:@","];
+ [params setValue:scope forKey:@"scope"];
}
-
- if (trySafariAuth && !didOpenOtherApp) {
- NSString *nextUrl = [self getOwnBaseUrl];
- [params setValue:nextUrl forKey:@"redirect_uri"];
-
- NSString *fbAppUrl = [FBRequest serializeURL:loginDialogURL params:params];
- didOpenOtherApp = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:fbAppUrl]];
+
+ if (_urlSchemeSuffix) {
+ [params setValue:_urlSchemeSuffix forKey:@"local_client_id"];
+ }
+
+ // If the device is running a version of iOS that supports multitasking,
+ // try to obtain the access token from the Facebook app installed
+ // on the device.
+ // If the Facebook app isn't installed or it doesn't support
+ // the fbauth:// URL scheme, fall back on Safari for obtaining the access token.
+ // This minimizes the chance that the user will have to enter his or
+ // her credentials in order to authorize the application.
+ BOOL didOpenOtherApp = NO;
+ UIDevice *device = [UIDevice currentDevice];
+ if ([device respondsToSelector:@selector(isMultitaskingSupported)] && [device isMultitaskingSupported]) {
+ if (tryFBAppAuth) {
+ NSString *scheme = kFBAppAuthURLScheme;
+ if (_urlSchemeSuffix) {
+ scheme = [scheme stringByAppendingString:@"2"];
+ }
+ NSString *urlPrefix = [NSString stringWithFormat:@"%@://%@", scheme, kFBAppAuthURLPath];
+ NSString *fbAppUrl = [FBRequest serializeURL:urlPrefix params:params];
+ didOpenOtherApp = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:fbAppUrl]];
+ }
+
+ if (trySafariAuth && !didOpenOtherApp) {
+ NSString *nextUrl = [self getOwnBaseUrl];
+ [params setValue:nextUrl forKey:@"redirect_uri"];
+
+ NSString *fbAppUrl = [FBRequest serializeURL:loginDialogURL params:params];
+ didOpenOtherApp = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:fbAppUrl]];
+ }
+ }
+
+ // If single sign-on failed, open an inline login dialog. This will require the user to
+ // enter his or her credentials.
+ if (!didOpenOtherApp) {
+ [_loginDialog release];
+ _loginDialog = [[FBLoginDialog alloc] initWithURL:loginDialogURL
+ loginParams:params
+ delegate:self];
+ [_loginDialog show];
}
- }
-
- // If single sign-on failed, open an inline login dialog. This will require the user to
- // enter his or her credentials.
- if (!didOpenOtherApp) {
- [_loginDialog release];
- _loginDialog = [[FBLoginDialog alloc] initWithURL:loginDialogURL
- loginParams:params
- delegate:self];
- [_loginDialog show];
- }
}
/**
@@ -321,9 +326,9 @@ - (NSDictionary*)parseURLParams:(NSString *)query {
* the user has logged in.
*/
- (void)authorize:(NSArray *)permissions {
- self.permissions = permissions;
-
- [self authorizeWithFBAppAuth:YES safariAuth:YES];
+ self.permissions = permissions;
+
+ [self authorizeWithFBAppAuth:YES safariAuth:YES];
}
/**
@@ -353,27 +358,27 @@ - (void)extendAccessToken {
* Calls extendAccessToken if shouldExtendAccessToken returns YES.
*/
- (void)extendAccessTokenIfNeeded {
- if ([self shouldExtendAccessToken]) {
- [self extendAccessToken];
- }
+ if ([self shouldExtendAccessToken]) {
+ [self extendAccessToken];
+ }
}
/**
* Returns YES if the last time a new token was obtained was over 24 hours ago.
*/
- (BOOL)shouldExtendAccessToken {
- if ([self isSessionValid]){
- NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
- NSDateComponents *components = [calendar components:NSHourCalendarUnit
- fromDate:_lastAccessTokenUpdate
- toDate:[NSDate date]
- options:0];
-
- if (components.hour >= kTokenExtendThreshold) {
- return YES;
+ if ([self isSessionValid]){
+ NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
+ NSDateComponents *components = [calendar components:NSHourCalendarUnit
+ fromDate:_lastAccessTokenUpdate
+ toDate:[NSDate date]
+ options:0];
+
+ if (components.hour >= kTokenExtendThreshold) {
+ return YES;
+ }
}
- }
- return NO;
+ return NO;
}
/**
@@ -394,65 +399,65 @@ - (BOOL)shouldExtendAccessToken {
* by SDK, NO otherwise.
*/
- (BOOL)handleOpenURL:(NSURL *)url {
- // If the URL's structure doesn't match the structure used for Facebook authorization, abort.
- if (![[url absoluteString] hasPrefix:[self getOwnBaseUrl]]) {
- return NO;
- }
-
- NSString *query = [url fragment];
-
- // Version 3.2.3 of the Facebook app encodes the parameters in the query but
- // version 3.3 and above encode the parameters in the fragment. To support
- // both versions of the Facebook app, we try to parse the query if
- // the fragment is missing.
- if (!query) {
- query = [url query];
- }
-
- NSDictionary *params = [self parseURLParams:query];
- NSString