Skip to content
Browse files

Facebook iOS SDK 3.7

  • Loading branch information...
1 parent 3ec37e0 commit 121e8841e4dfb83847184892cdb6315e08241a1d @chrisp-fb chrisp-fb committed
Showing with 2,142 additions and 427 deletions.
  1. +48 −24 samples/HelloFacebookSample/HelloFacebookSample/HFViewController.m
  2. +2 −2 samples/RPSSample/RPSSample/RPSSample-Info.plist
  3. BIN samples/Scrumptious/scrumptious/FacebookSDKOverrides.bundle/FacebookSDKImages/FBLoginViewButton.png
  4. BIN ...es/Scrumptious/scrumptious/FacebookSDKOverrides.bundle/FacebookSDKImages/FBLoginViewButton@2x.png
  5. BIN ...mptious/scrumptious/FacebookSDKOverrides.bundle/FacebookSDKImages/FBLoginViewLoginButtonSmall.png
  6. BIN samples/Scrumptious/scrumptious/FacebookSDKOverrides.bundle/en.lproj/Localizable.strings
  7. +1 −1 samples/Scrumptious/scrumptious/SCAppDelegate.m
  8. +24 −62 samples/Scrumptious/scrumptious/SCLoginViewController.xib
  9. +5 −4 samples/Scrumptious/scrumptious/SCViewController.m
  10. +1 −1 scripts/build_distribution.sh
  11. +1 −1 scripts/build_documentation.sh
  12. +2 −1 src/FBAppBridge.m
  13. +18 −0 src/FBAppEvents+Internal.h
  14. +14 −7 src/FBAppEvents.m
  15. +4 −2 src/FBDataDiskCache.m
  16. +12 −0 src/FBError.h
  17. +1 −0 src/FBError.m
  18. +2 −0 src/FBErrorUtility+Internal.h
  19. +1 −1 src/FBErrorUtility.m
  20. +2 −0 src/FBFetchedAppSettings.h
  21. +26 −5 src/FBFetchedAppSettings.m
  22. +4 −5 src/FBLoginDialogParams.m
  23. +66 −28 src/FBLoginView.m
  24. +9 −0 src/FBRequestConnection+Internal.h
  25. +60 −0 src/FBRequestConnection.h
  26. +114 −91 src/FBRequestConnection.m
  27. +80 −0 src/FBRequestConnectionRetryManager.h
  28. +197 −0 src/FBRequestConnectionRetryManager.m
  29. +18 −0 src/FBRequestHandlerFactory.h
  30. +146 −0 src/FBRequestHandlerFactory.m
  31. +45 −0 src/FBRequestMetadata.h
  32. +82 −0 src/FBRequestMetadata.m
  33. +1 −1 src/FBSDKVersion.h
  34. +8 −0 src/FBSession+Internal.h
  35. +448 −124 src/FBSession.m
  36. +95 −0 src/FBSessionAuthLogger.h
  37. +172 −0 src/FBSessionAuthLogger.m
  38. +3 −0 src/FBTestSession.h
  39. +2 −1 src/FBTestSession.m
  40. +15 −0 src/FBUserSettingsViewController.m
  41. +2 −0 src/FBUtility.h
  42. +40 −12 src/FBUtility.m
  43. +2 −2 src/FBWebDialogs.h
  44. +8 −2 src/FBWebDialogs.m
  45. +1 −1 src/FacebookSDK.h
  46. +45 −27 src/FacebookSDKIntegrationTests/FBAppEventsIntegrationTests.m
  47. +1 −0 src/FacebookSDKResources.bundle.README
  48. BIN src/ImageResources/FBLoginView/FBLoginViewButton.png
  49. BIN src/ImageResources/FBLoginView/FBLoginViewButton@2x.png
  50. BIN src/ImageResources/FBLoginView/FBLoginViewButtonPressed.png
  51. BIN src/ImageResources/FBLoginView/FBLoginViewButtonPressed@2x.png
  52. BIN src/ImageResources/FBLoginView/FBLoginViewLoginButtonSmall.png
  53. BIN src/ImageResources/FBLoginView/FBLoginViewLoginButtonSmall@2x.png
  54. BIN src/ImageResources/FBLoginView/FBLoginViewLoginButtonSmallPressed.png
  55. BIN src/ImageResources/FBLoginView/FBLoginViewLoginButtonSmallPressed@2x.png
  56. +66 −22 src/facebook-ios-sdk.xcodeproj/project.pbxproj
  57. +248 −0 src/tests/FBRequestConnectionTests.m
View
72 samples/HelloFacebookSample/HelloFacebookSample/HFViewController.m
@@ -154,8 +154,14 @@ - (void) performPublishAction:(void (^)(void)) action {
completionHandler:^(FBSession *session, NSError *error) {
if (!error) {
action();
+ } else if (error.fberrorCategory != FBErrorCategoryUserCancelled){
+ UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Permission denied"
+ message:@"Unable to get permission to post"
+ delegate:nil
+ cancelButtonTitle:@"OK"
+ otherButtonTitles:nil];
+ [alertView show];
}
- //For this example, ignore errors (such as if user cancels).
}];
} else {
action();
@@ -211,12 +217,19 @@ - (IBAction)postStatusUpdateClick:(UIButton *)sender {
[self performPublishAction:^{
NSString *message = [NSString stringWithFormat:@"Updating status for %@ at %@", self.loggedInUser.first_name, [NSDate date]];
- [FBRequestConnection startForPostStatusUpdate:message
- completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
-
- [self showAlert:message result:result error:error];
- self.buttonPostStatus.enabled = YES;
- }];
+ FBRequestConnection *connection = [[FBRequestConnection alloc] init];
+
+ connection.errorBehavior = FBRequestConnectionErrorBehaviorReconnectSession
+ | FBRequestConnectionErrorBehaviorAlertUser
+ | FBRequestConnectionErrorBehaviorRetry;
+
+ [connection addRequest:[FBRequest requestForPostStatusUpdate:message]
+ completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
+
+ [self showAlert:message result:result error:error];
+ self.buttonPostStatus.enabled = YES;
+ }];
+ [connection start];
self.buttonPostStatus.enabled = NO;
}];
@@ -231,12 +244,20 @@ - (IBAction)postPhotoClick:(UIButton *)sender {
UIImage *img = [UIImage imageNamed:@"Icon-72@2x.png"];
[self performPublishAction:^{
+ FBRequestConnection *connection = [[FBRequestConnection alloc] init];
+ connection.errorBehavior = FBRequestConnectionErrorBehaviorReconnectSession
+ | FBRequestConnectionErrorBehaviorAlertUser
+ | FBRequestConnectionErrorBehaviorRetry;
- [FBRequestConnection startForUploadPhoto:img
- completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
- [self showAlert:@"Photo Post" result:result error:error];
- self.buttonPostPhoto.enabled = YES;
- }];
+ [connection addRequest:[FBRequest requestForUploadPhoto:img]
+ completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
+
+ [self showAlert:@"Photo Post" result:result error:error];
+ if (FBSession.activeSession.isOpen) {
+ self.buttonPostPhoto.enabled = YES;
+ }
+ }];
+ [connection start];
self.buttonPostPhoto.enabled = NO;
}];
@@ -322,13 +343,14 @@ - (void)showAlert:(NSString *)message
NSString *alertTitle;
if (error) {
alertTitle = @"Error";
- // For simplicity, we will use any error message provided by the SDK,
- // but you may consider inspecting the fberrorShouldNotifyUser or
- // fberrorCategory to provide better recourse to users. See the Scrumptious
- // sample for more examples on error handling.
- if (error.fberrorUserMessage) {
- alertMsg = error.fberrorUserMessage;
+ // Since we use FBRequestConnectionErrorBehaviorAlertUser,
+ // we do not need to surface our own alert view if there is an
+ // an fberrorUserMessage unless the session is closed.
+ if (error.fberrorUserMessage && FBSession.activeSession.isOpen) {
+ alertTitle = nil;
+
} else {
+ // Otherwise, use a general "connection problem" message.
alertMsg = @"Operation failed due to a connection problem, retry later.";
}
} else {
@@ -344,12 +366,14 @@ - (void)showAlert:(NSString *)message
alertTitle = @"Success";
}
- UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:alertTitle
- message:alertMsg
- delegate:nil
- cancelButtonTitle:@"OK"
- otherButtonTitles:nil];
- [alertView show];
+ if (alertTitle) {
+ UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:alertTitle
+ message:alertMsg
+ delegate:nil
+ cancelButtonTitle:@"OK"
+ otherButtonTitles:nil];
+ [alertView show];
+ }
}
View
4 samples/RPSSample/RPSSample/RPSSample-Info.plist
@@ -45,7 +45,7 @@
<dict>
<key>CFBundleURLSchemes</key>
<array>
- <string>fb157578437735213Free</string>
+ <string>fb157578437735213free</string>
<string>fb157578437735213</string>
</array>
</dict>
@@ -57,7 +57,7 @@
<key>FacebookDisplayName</key>
<string>Rock Paper Scissors SDK Sample</string>
<key>FacebookUrlSchemeSuffix</key>
- <string>Free</string>
+ <string>free</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
View
BIN ...rumptious/scrumptious/FacebookSDKOverrides.bundle/FacebookSDKImages/FBLoginViewButton.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN ...ptious/scrumptious/FacebookSDKOverrides.bundle/FacebookSDKImages/FBLoginViewButton@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN ...scrumptious/FacebookSDKOverrides.bundle/FacebookSDKImages/FBLoginViewLoginButtonSmall.png
Deleted file not rendered
View
BIN samples/Scrumptious/scrumptious/FacebookSDKOverrides.bundle/en.lproj/Localizable.strings
Binary file not shown.
View
2 samples/Scrumptious/scrumptious/SCAppDelegate.m
@@ -79,7 +79,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
self.navigationController = [[UINavigationController alloc] initWithRootViewController:self.loginViewController];
self.navigationController.delegate = self;
self.window.rootViewController = self.navigationController;
-
+
[self.window makeKeyAndVisible];
// Facebook SDK * pro-tip *
View
86 samples/Scrumptious/scrumptious/SCLoginViewController.xib
@@ -2,9 +2,9 @@
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="8.00">
<data>
<int key="IBDocument.SystemTarget">1552</int>
- <string key="IBDocument.SystemVersion">12D78</string>
+ <string key="IBDocument.SystemVersion">12E55</string>
<string key="IBDocument.InterfaceBuilderVersion">3084</string>
- <string key="IBDocument.AppKitVersion">1187.37</string>
+ <string key="IBDocument.AppKitVersion">1187.39</string>
<string key="IBDocument.HIToolboxVersion">626.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
@@ -44,10 +44,10 @@
<object class="IBUIView" id="248182986">
<reference key="NSNextResponder" ref="896580239"/>
<int key="NSvFlags">301</int>
- <string key="NSFrame">{{11, 98}, {183, 39}}</string>
+ <string key="NSFrame">{{-7, 87}, {218, 46}}</string>
<reference key="NSSuperview" ref="896580239"/>
<reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="389419541"/>
+ <reference key="NSNextKeyView" ref="359402499"/>
<string key="NSReuseIdentifierKey">_NS:9</string>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">3</int>
@@ -72,7 +72,7 @@
<int key="IBUIContentMode">7</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
- <string key="IBUIText">To get started, Log In using Facebook</string>
+ <string key="IBUIText">Start sharing meals you eat</string>
<object class="NSColor" key="IBUITextColor">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MC45NDM4Nzc1NTEgMC45NDM4Nzc1NTEgMC45NDM4Nzc1NTEAA</bytes>
@@ -82,11 +82,11 @@
<float key="IBUIMinimumFontSize">10</float>
<int key="IBUINumberOfLines">0</int>
<int key="IBUITextAlignment">1</int>
- <object class="IBUIFontDescription" key="IBUIFontDescription" id="741649715">
+ <object class="IBUIFontDescription" key="IBUIFontDescription">
<int key="type">1</int>
<double key="pointSize">17</double>
</object>
- <object class="NSFont" key="IBUIFont" id="112027661">
+ <object class="NSFont" key="IBUIFont">
<string key="NSName">Helvetica</string>
<double key="NSSize">17</double>
<int key="NSfFlags">16</int>
@@ -96,7 +96,7 @@
<object class="IBUIButton" id="359402499">
<reference key="NSNextResponder" ref="896580239"/>
<int key="NSvFlags">301</int>
- <string key="NSFrame">{{69, 149}, {66, 23}}</string>
+ <string key="NSFrame">{{12, 147}, {180, 23}}</string>
<reference key="NSSuperview" ref="896580239"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView"/>
@@ -105,7 +105,7 @@
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<int key="IBUIContentHorizontalAlignment">0</int>
<int key="IBUIContentVerticalAlignment">0</int>
- <string key="IBUINormalTitle">Skip Log In</string>
+ <string key="IBUINormalTitle">(Or click here to skip login)</string>
<object class="NSColor" key="IBUINormalTitleColor" id="269655657">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MQA</bytes>
@@ -123,31 +123,8 @@
<int key="NSfFlags">16</int>
</object>
</object>
- <object class="IBUILabel" id="389419541">
- <reference key="NSNextResponder" ref="896580239"/>
- <int key="NSvFlags">301</int>
- <string key="NSFrame">{{68, 167.5}, {68, 1}}</string>
- <reference key="NSSuperview" ref="896580239"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="359402499"/>
- <string key="NSReuseIdentifierKey">_NS:9</string>
- <reference key="IBUIBackgroundColor" ref="269655657"/>
- <bool key="IBUIOpaque">NO</bool>
- <bool key="IBUIClipsSubviews">YES</bool>
- <int key="IBUIContentMode">7</int>
- <bool key="IBUIUserInteractionEnabled">NO</bool>
- <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
- <string key="IBUIText"/>
- <reference key="IBUITextColor" ref="269655657"/>
- <reference key="IBUIHighlightedColor" ref="269655657"/>
- <reference key="IBUIShadowColor" ref="269655657"/>
- <int key="IBUIBaselineAdjustment">0</int>
- <reference key="IBUIFontDescription" ref="741649715"/>
- <reference key="IBUIFont" ref="112027661"/>
- <bool key="IBUIAdjustsFontSizeToFit">NO</bool>
- </object>
</array>
- <string key="NSFrame">{{58, 148}, {204, 176}}</string>
+ <string key="NSFrame">{{58, 115}, {204, 176}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="599947327"/>
@@ -205,10 +182,6 @@
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="144313356"/>
<string key="NSReuseIdentifierKey">_NS:9</string>
- <object class="NSColor" key="IBUIBackgroundColor">
- <int key="NSColorSpace">1</int>
- <bytes key="NSRGB">MSAxIDEAA</bytes>
- </object>
<int key="IBUIContentMode">4</int>
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
@@ -291,8 +264,8 @@
<int key="objectID">1</int>
<reference key="object" ref="191373211"/>
<array class="NSMutableArray" key="children">
- <reference ref="896580239"/>
<reference ref="481110768"/>
+ <reference ref="896580239"/>
</array>
<reference key="parent" ref="0"/>
</object>
@@ -312,19 +285,12 @@
<reference key="object" ref="896580239"/>
<array class="NSMutableArray" key="children">
<reference ref="599947327"/>
- <reference ref="248182986"/>
- <reference ref="389419541"/>
<reference ref="359402499"/>
+ <reference ref="248182986"/>
</array>
<reference key="parent" ref="191373211"/>
</object>
<object class="IBObjectRecord">
- <int key="objectID">13</int>
- <reference key="object" ref="248182986"/>
- <array class="NSMutableArray" key="children"/>
- <reference key="parent" ref="896580239"/>
- </object>
- <object class="IBObjectRecord">
<int key="objectID">4</int>
<reference key="object" ref="599947327"/>
<reference key="parent" ref="896580239"/>
@@ -333,8 +299,8 @@
<int key="objectID">18</int>
<reference key="object" ref="481110768"/>
<array class="NSMutableArray" key="children">
- <reference ref="113215792"/>
<reference ref="144313356"/>
+ <reference ref="113215792"/>
</array>
<reference key="parent" ref="191373211"/>
</object>
@@ -354,8 +320,8 @@
<reference key="parent" ref="896580239"/>
</object>
<object class="IBObjectRecord">
- <int key="objectID">30</int>
- <reference key="object" ref="389419541"/>
+ <int key="objectID">13</int>
+ <reference key="object" ref="248182986"/>
<reference key="parent" ref="896580239"/>
</object>
</array>
@@ -373,7 +339,6 @@
<string key="19.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<real value="0.0" key="19.IBUIButtonInspectorSelectedEdgeInsetMetadataKey"/>
<real value="0.0" key="19.IBUIButtonInspectorSelectedStateConfigurationMetadataKey"/>
- <string key="30.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="4.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="5.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="6.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
@@ -382,7 +347,7 @@
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
- <int key="maxID">30</int>
+ <int key="maxID">34</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
@@ -419,20 +384,17 @@
<string key="candidateClassName">id</string>
</object>
</object>
- <dictionary class="NSMutableDictionary" key="outlets">
- <string key="FBLoginView">FBLoginView</string>
- <string key="skipLogInButton">UIButton</string>
- </dictionary>
- <dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
- <object class="IBToOneOutletInfo" key="FBLoginView">
+ <object class="NSMutableDictionary" key="outlets">
+ <string key="NS.key.0">FBLoginView</string>
+ <string key="NS.object.0">FBLoginView</string>
+ </object>
+ <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <string key="NS.key.0">FBLoginView</string>
+ <object class="IBToOneOutletInfo" key="NS.object.0">
<string key="name">FBLoginView</string>
<string key="candidateClassName">FBLoginView</string>
</object>
- <object class="IBToOneOutletInfo" key="skipLogInButton">
- <string key="name">skipLogInButton</string>
- <string key="candidateClassName">UIButton</string>
- </object>
- </dictionary>
+ </object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/SCLoginViewController.h</string>
View
9 samples/Scrumptious/scrumptious/SCViewController.m
@@ -138,6 +138,8 @@ - (void)postOpenGraphAction {
[self enableUserInteraction:NO];
FBRequestConnection *requestConnection = [[FBRequestConnection alloc] init];
+ requestConnection.errorBehavior = FBRequestConnectionErrorBehaviorRetry
+ | FBRequestConnectionErrorBehaviorReconnectSession;
if (self.selectedPhoto) {
self.selectedPhoto = [self normalizedImage:self.selectedPhoto];
FBRequest *stagingRequest = [FBRequest requestForUploadStagingResourceWithImage:self.selectedPhoto];
@@ -227,8 +229,7 @@ - (void)handlePostOpenGraphActionError:(NSError *) error{
// retry policy of one additional attempt. Please refer to
// https://developers.facebook.com/docs/reference/api/errors/ for more information.
_retryCount++;
- if (error.fberrorCategory == FBErrorCategoryRetry ||
- error.fberrorCategory == FBErrorCategoryThrottling) {
+ if (error.fberrorCategory == FBErrorCategoryThrottling) {
// We also retry on a throttling error message. A more sophisticated app
// should consider a back-off period.
if (_retryCount < 2) {
@@ -259,10 +260,10 @@ - (void)requestPermissionAndPost {
[FBSession.activeSession requestNewPublishPermissions:[NSArray arrayWithObject:@"publish_actions"]
defaultAudience:FBSessionDefaultAudienceFriends
completionHandler:^(FBSession *session, NSError *error) {
- if (!error) {
+ if (!error && [FBSession.activeSession.permissions indexOfObject:@"publish_actions"] != NSNotFound) {
// Now have the permission
[self postOpenGraphAction];
- } else {
+ } else if (error){
// Facebook SDK * error handling *
// if the operation is not user cancelled
if (error.fberrorCategory != FBErrorCategoryUserCancelled) {
View
2 scripts/build_distribution.sh
@@ -87,7 +87,7 @@ $PACKAGEMAKER \
--target 10.5 \
--version $FB_SDK_VERSION \
--out $FB_SDK_UNSIGNED_PKG \
- --title 'Facebook SDK 3.6 for iOS' \
+ --title 'Facebook SDK 3.7 for iOS' \
|| die "PackageMaker reported error"
progress_message "Signing package."
View
2 scripts/build_documentation.sh
@@ -45,7 +45,7 @@ rm -rf $DOCSET
hash $APPLEDOC &>/dev/null
if [ "$?" -eq "0" ]; then
- APPLEDOC_DOCSET_NAME="Facebook SDK 3.6 for iOS"
+ APPLEDOC_DOCSET_NAME="Facebook SDK 3.7 for iOS"
$APPLEDOC --project-name "$APPLEDOC_DOCSET_NAME" \
--project-company "Facebook" \
--company-id "com.facebook" \
View
3 src/FBAppBridge.m
@@ -112,7 +112,8 @@
*/
static NSString *const FBAppBridgeVersions[] = {
@"20130214",
- @"20130410"
+ @"20130410",
+ @"20130702"
};
static FBAppBridge *g_sharedInstance;
View
18 src/FBAppEvents+Internal.h
@@ -38,6 +38,12 @@ extern NSString *const FBAppEventNameFriendPickerUsage;
/*! Use to log that the place picker dialog was launched and completed */
extern NSString *const FBAppEventNamePlacePickerUsage;
+/*! Use to log that the login view was used */
+extern NSString *const FBAppEventNameLoginViewUsage;
+
+/*! Use to log that the user settings view controller was used */
+extern NSString *const FBAppEventNameUserSettingsUsage;
+
// Internally known event parameters
/*! String parameter specifying the outcome of a dialog invocation */
@@ -49,6 +55,18 @@ extern NSString *const FBAppEventNameFBDialogsCanPresentShareDialog;
/*! Use to log the result of a call to FBDialogs canPresentShareDialogWithOpenGraphActionParams: */
extern NSString *const FBAppEventNameFBDialogsCanPresentShareDialogOG;
+/*! Use to log the start of an auth request that cannot be fulfilled by the token cache */
+extern NSString *const FBAppEventNameFBSessionAuthStart;
+
+/*! Use to log the end of an auth request that was not fulfilled by the token cache */
+extern NSString *const FBAppEventNameFBSessionAuthEnd;
+
+/*! Use to log the start of a specific auth method as part of an auth request */
+extern NSString *const FBAppEventNameFBSessionAuthMethodStart;
+
+/*! Use to log the end of the last tried auth method as part of an auth request */
+extern NSString *const FBAppEventNameFBSessionAuthMethodEnd;
+
/*! Use to log the timestamp for the transition to the Facebook native login dialog */
extern NSString *const FBAppEventNameFBDialogsNativeLoginDialogStart;
View
21 src/FBAppEvents.m
@@ -80,6 +80,8 @@
NSString *const FBAppEventNameLogConversionPixel = @"fb_log_offsite_pixel";
NSString *const FBAppEventNameFriendPickerUsage = @"fb_friend_picker_usage";
NSString *const FBAppEventNamePlacePickerUsage = @"fb_place_picker_usage";
+NSString *const FBAppEventNameLoginViewUsage = @"fb_login_view_usage";
+NSString *const FBAppEventNameUserSettingsUsage = @"fb_user_settings_vc_usage";
NSString *const FBAppEventNameShareSheetLaunch = @"fb_share_sheet_launch";
NSString *const FBAppEventNameShareSheetDismiss = @"fb_share_sheet_dismiss";
NSString *const FBAppEventNamePermissionsUILaunch = @"fb_permissions_ui_launch";
@@ -97,6 +99,11 @@
NSString *const FBAppEventsWebLoginE2E = @"fb_web_login_e2e";
NSString *const FBAppEventsWebLoginSwitchbackTime = @"fb_web_login_switchback_time";
+NSString *const FBAppEventNameFBSessionAuthStart = @"fb_mobile_login_start";
+NSString *const FBAppEventNameFBSessionAuthEnd = @"fb_mobile_login_complete";
+NSString *const FBAppEventNameFBSessionAuthMethodStart = @"fb_mobile_login_method_start";
+NSString *const FBAppEventNameFBSessionAuthMethodEnd = @"fb_mobile_login_method_complete";
+
// Event Parameters internal to this file
NSString *const FBAppEventParameterConversionPixelID = @"fb_offsite_pixel_id";
NSString *const FBAppEventParameterConversionPixelValue = @"fb_offsite_pixel_value";
@@ -323,14 +330,14 @@ + (void)setLimitEventUsage:(BOOL)limitEventUsage {
}
+ (void)activateApp {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ // activateApp supercedes publishInstall in the public API, but we need to
+ // trigger an install event as before.
+ [FBSettings publishInstall:nil];
+#pragma clang diagnostic pop
+
[FBAppEvents logEvent:FBAppEventNameActivatedApp];
- NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
- NSString *appFlushKey = [NSString stringWithFormat:FBAppEventsActivateAppFlush, [FBSettings defaultAppID], nil];
- if ([FBAppEvents flushBehavior] != FBAppEventsFlushBehaviorExplicitOnly && [defaults objectForKey:appFlushKey] == nil) {
- [FBAppEvents.singleton instanceFlush:FBAppEventsFlushReasonEagerlyFlushingEvent];
- [defaults setObject:[NSDate date] forKey:appFlushKey];
- [defaults synchronize];
- }
}
#pragma mark - Flushing & Session Management
View
6 src/FBDataDiskCache.m
@@ -78,8 +78,10 @@ - (void)dealloc
if (_fileQueue) {
dispatch_release(_fileQueue);
}
-
- [_cacheIndex release];
+ if (_cacheIndex) {
+ _cacheIndex.delegate = nil;
+ [_cacheIndex release];
+ }
[_dataCachePath release];
[_inMemoryCache release];
[super dealloc];
View
12 src/FBError.h
@@ -113,6 +113,11 @@ typedef enum FBErrorCode {
The URL passed to FBAppCall, was not able to be parsed
*/
FBErrorMalformedURL,
+
+ /*!
+ The operation failed because the session is currently busy reconnecting.
+ */
+ FBErrorSessionReconnectInProgess,
} FBErrorCode;
/*!
@@ -243,6 +248,13 @@ extern NSString *const FBErrorLoginFailedReasonUserCancelledSystemValue;
/*!
A value that may appear in an NSError userInfo dictionary under the
+ `FBErrorLoginFailedReason` key for login failures. Indicates an error
+ condition. You may inspect the rest of userInfo for other data.
+ */
+extern NSString *const FBErrorLoginFailedReasonOtherError;
+
+/*!
+ A value that may appear in an NSError userInfo dictionary under the
`FBErrorLoginFailedReason` key for login failures. Indicates the app's
slider in iOS 6 (device Settings -> Privacy -> Facebook {app} ) has
been disabled.
View
1 src/FBError.m
@@ -32,6 +32,7 @@
NSString *const FBErrorLoginFailedReasonInlineNotCancelledValue = @"com.facebook.sdk:ErrorLoginNotCancelled";
NSString *const FBErrorLoginFailedReasonUserCancelledValue = @"com.facebook.sdk:UserLoginCancelled";
NSString *const FBErrorLoginFailedReasonUserCancelledSystemValue = @"com.facebook.sdk:SystemLoginCancelled";
+NSString *const FBErrorLoginFailedReasonOtherError = @"com.facebook.sdk:UserLoginOtherError";
NSString *const FBErrorLoginFailedReasonSystemDisallowedWithoutErrorValue = @"com.facebook.sdk:SystemLoginDisallowedWithoutError";
NSString *const FBErrorLoginFailedReasonSystemError = @"com.facebook.sdk:SystemLoginError";
View
2 src/FBErrorUtility+Internal.h
@@ -26,6 +26,8 @@ typedef enum {
FBAuthSubcodeUnconfirmedUser = 464,
} FBAuthSubcode;
+extern const int FBOAuthError;
+
// Internal class collecting error related methods.
@interface FBErrorUtility(Internal)
View
2 src/FBErrorUtility.m
@@ -20,7 +20,7 @@
#import "FBUtility.h"
#import "FBAccessTokenData+Internal.h"
-static const int FBOAuthError = 190;
+const int FBOAuthError = 190;
static const int FBAPISessionError = 102;
static const int FBAPIServiceError = 2;
static const int FBAPIUnknownError = 1;
View
2 src/FBFetchedAppSettings.h
@@ -24,6 +24,8 @@
@property (readwrite) BOOL supportsAttribution;
@property (readwrite) BOOL supportsImplicitSdkLogging;
@property (readwrite) BOOL suppressNativeGdp;
+@property (readonly, nonatomic) NSString *appID;
+-(id) initWithAppID:(NSString *)appID;
@end
View
31 src/FBFetchedAppSettings.m
@@ -14,14 +14,35 @@
* limitations under the License.
*/
+#import "FBSettings.h"
#import "FBFetchedAppSettings.h"
+@interface FBFetchedAppSettings()
+
+@property (readwrite, retain, nonatomic) NSString *appID;
+
+@end
+
@implementation FBFetchedAppSettings
-@synthesize
- serverAppName = _serverAppName,
- supportsAttribution = _supportsAttribution,
- supportsImplicitSdkLogging = _supportsImplicitSdkLogging,
- suppressNativeGdp = _suppressNativeGdp;
+-(id) init {
+ return [self initWithAppID:nil];
+}
+
+-(id) initWithAppID:(NSString *)appID {
+ if (self = [super init]) {
+ if (appID == nil) {
+ appID = [FBSettings defaultAppID];
+ }
+ self.appID = appID;
+ }
+ return self;
+}
+-(void) dealloc {
+ self.serverAppName = nil;
+ self.appID = nil;
+
+ [super dealloc];
+}
@end
View
9 src/FBLoginDialogParams.m
@@ -24,8 +24,7 @@
static NSString *const SSOWritePrivacyFriends = @"ALL_FRIENDS";
static NSString *const SSOWritePrivacyOnlyMe = @"SELF";
-static NSString *const kFBNativeLoginMinVersion = @"20130214";
-static NSString *const kFBNativeLoginWithoutNameVersion = @"20130410"; // This is currently unused. Leaving here for doc purposes.
+static NSString *const kFBNativeLoginMinVersion = @"20130702";
@implementation FBLoginDialogParams
@@ -72,9 +71,9 @@ - (NSDictionary *)dictionaryMethodArgs
args[@"write_privacy"] = writePrivacyString;
}
- if (self.isRefreshOnly) {
- args[@"is_refresh_only"] = @"1";
- }
+ // to support token-import we always try the refresh flow. It will fallback to the permissions
+ // dialog if the app is not installed or does not have the necessary permissions
+ args[@"is_refresh_only"] = @"1";
return args;
}
View
94 src/FBLoginView.m
@@ -22,13 +22,22 @@
#import "FBGraphUser.h"
#import "FBUtility.h"
#import "FBSession+Internal.h"
-#import "FBLoginViewLoginButtonSmallPNG.h"
-#import "FBLoginViewLoginButtonSmallPressedPNG.h"
+#import "FBAppEvents+Internal.h"
+#import "FBLoginViewButtonPNG.h"
+#import "FBLoginViewButtonPressedPNG.h"
static NSString *const FBLoginViewCacheIdentity = @"FBLoginView";
-const int kButtonLabelX = 46;
+// The design calls for 16 pixels of space on the right edge of the button
+static const float kButtonEndCapWidth = 16.0;
+// The button has a 12 pixel buffer to the right of the f logo
+static const float kButtonPaddingWidth = 12.0;
-CGSize g_imageSize;
+static CGSize g_buttonSize;
+
+// Forward declare our label wrapper that provides shadow blur
+@interface FBShadowLabel : UILabel
+
+@end
@interface FBLoginView() <UIActionSheetDelegate>
@@ -198,6 +207,7 @@ - (void)initializeBlocks {
weakSelf.request = nil;
};
}
+
- (void)initialize {
// the base class can cause virtual recursion, so
// to handle this we make initialize idempotent
@@ -240,31 +250,37 @@ - (void)initialize {
self.button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentFill;
self.button.autoresizingMask = UIViewAutoresizingFlexibleWidth;
- UIImage *image = [[FBLoginViewLoginButtonSmallPNG image]
- stretchableImageWithLeftCapWidth:kButtonLabelX topCapHeight:0];
- g_imageSize = image.size;
+ // We want to make sure that when we stretch the image, it includes the curved edges and drop shadow
+ // We inset enough pixels to make sure that happens
+ UIEdgeInsets imageInsets = UIEdgeInsetsMake(4.0, 40.0, 4.0, 4.0);
+
+ UIImage *image = [[FBLoginViewButtonPNG image] resizableImageWithCapInsets:imageInsets];
[self.button setBackgroundImage:image forState:UIControlStateNormal];
- image = [[FBLoginViewLoginButtonSmallPressedPNG image]
- stretchableImageWithLeftCapWidth:kButtonLabelX topCapHeight:0];
+ image = [[FBLoginViewButtonPressedPNG image] resizableImageWithCapInsets:imageInsets];
[self.button setBackgroundImage:image forState:UIControlStateHighlighted];
-
+
[self addSubview:self.button];
+
+ // Compute the text size to figure out the overall size of the button
+ UIFont *font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:14.0];
+ float textSizeWidth = MAX([[self logInText] sizeWithFont:font].width, [[self logOutText] sizeWithFont:font].width);
+
+ // We make the button big enough to hold the image, the text, the padding to the right of the f and the end cap
+ g_buttonSize = CGSizeMake(image.size.width + textSizeWidth + kButtonPaddingWidth + kButtonEndCapWidth, image.size.height);
// add a label that will appear over the button
- self.label = [[[UILabel alloc] init] autorelease];
+ self.label = [[[FBShadowLabel alloc] init] autorelease];
self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.label.textAlignment = UITextAlignmentCenter;
self.label.backgroundColor = [UIColor clearColor];
- self.label.font = [UIFont boldSystemFontOfSize:16.0];
+ self.label.font = font;
self.label.textColor = [UIColor whiteColor];
- self.label.shadowColor = [UIColor blackColor];
- self.label.shadowOffset = CGSizeMake(0.0, -1.0);
[self addSubview:self.label];
// We force our height to be the same as the image, but we will let someone make us wider
// than the default image.
- CGFloat width = MAX(self.frame.size.width, g_imageSize.width);
+ CGFloat width = MAX(self.frame.size.width, g_buttonSize.width);
CGRect frame = CGRectMake(self.frame.origin.x, self.frame.origin.y,
width, image.size.height);
self.frame = frame;
@@ -272,7 +288,8 @@ - (void)initialize {
CGRect buttonFrame = CGRectMake(0, 0, width, image.size.height);
self.button.frame = buttonFrame;
- self.label.frame = CGRectMake(kButtonLabelX, 0, width - kButtonLabelX, image.size.height);
+ // This needs to start at an x just to the right of the f in the image, the -1 on both x and y is to account for shadow in the image
+ self.label.frame = CGRectMake(image.size.width - kButtonPaddingWidth - 1, -1, width - (image.size.width - kButtonPaddingWidth) - kButtonEndCapWidth, image.size.height);
self.backgroundColor = [UIColor clearColor];
@@ -285,23 +302,15 @@ - (void)initialize {
}
- (CGSize)sizeThatFits:(CGSize)size {
- CGSize logInSize = [[self logInText] sizeWithFont:self.label.font];
- CGSize logOutSize = [[self logOutText] sizeWithFont:self.label.font];
-
- // Leave at least a small margin around the label.
- CGFloat desiredWidth = kButtonLabelX + 20 + MAX(logInSize.width, logOutSize.width);
- // Never get smaller than the image
- CGFloat width = MAX(desiredWidth, g_imageSize.width);
-
- return CGSizeMake(width, g_imageSize.height);
+ return CGSizeMake(g_buttonSize.width, g_buttonSize.height);
}
- (NSString *)logInText {
- return [FBUtility localizedStringForKey:@"FBLV:LogInButton" withDefault:@"Log In"];
+ return [FBUtility localizedStringForKey:@"FBLV:LogInButton" withDefault:@"Log in with Facebook"];
}
- (NSString *)logOutText {
- return [FBUtility localizedStringForKey:@"FBLV:LogOutButton" withDefault:@"Log Out"];
+ return [FBUtility localizedStringForKey:@"FBLV:LogOutButton" withDefault:@"Log out"];
}
- (void)configureViewForStateLoggedIn:(BOOL)isLoggedIn {
@@ -431,6 +440,7 @@ - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSIn
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
- (void)buttonPressed:(id)sender {
if (self.session == FBSession.activeSession) {
+ BOOL loggingInLogFlag = NO;
if (!self.session.isOpen) { // login
// the policy here is:
@@ -443,6 +453,7 @@ - (void)buttonPressed:(id)sender {
if (self.permissions) {
[FBSession openActiveSessionWithPermissions:self.permissions
allowLoginUI:YES
+ defaultAudience:self.defaultAudience
completionHandler:self.sessionStateHandler];
} else if (![self.publishPermissions count]) {
[FBSession openActiveSessionWithReadPermissions:self.readPermissions
@@ -462,6 +473,7 @@ - (void)buttonPressed:(id)sender {
allowLoginUI:YES
completionHandler:self.sessionStateHandler];
}
+ loggingInLogFlag = YES;
} else { // logout action sheet
NSString *name = self.user.name;
NSString *title = nil;
@@ -476,7 +488,7 @@ - (void)buttonPressed:(id)sender {
NSString *cancelTitle = [FBUtility localizedStringForKey:@"FBLV:CancelAction"
withDefault:@"Cancel"];
NSString *logOutTitle = [FBUtility localizedStringForKey:@"FBLV:LogOutAction"
- withDefault:@"Log Out"];
+ withDefault:@"Log out"];
UIActionSheet *sheet = [[[UIActionSheet alloc] initWithTitle:title
delegate:self
cancelButtonTitle:cancelTitle
@@ -486,6 +498,10 @@ - (void)buttonPressed:(id)sender {
// Show the sheet
[sheet showInView:self];
}
+ [FBAppEvents logImplicitEvent:FBAppEventNameLoginViewUsage
+ valueToSum:nil
+ parameters:@{ @"logging_in" : [NSNumber numberWithBool:loggingInLogFlag] }
+ session:nil];
} else { // state of view out of sync with active session
// so resync
[self unwireViewForSession];
@@ -496,3 +512,25 @@ - (void)buttonPressed:(id)sender {
}
#pragma GCC diagnostic warning "-Wdeprecated-declarations"
@end
+
+@implementation FBShadowLabel
+
+- (void) drawTextInRect:(CGRect)rect {
+ CGSize myShadowOffset = CGSizeMake(0, -1);
+ float myColorValues[] = {0, 0, 0, .3};
+
+ CGContextRef myContext = UIGraphicsGetCurrentContext();
+ CGContextSaveGState(myContext);
+
+ CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB();
+ CGColorRef myColor = CGColorCreate(myColorSpace, myColorValues);
+ CGContextSetShadowWithColor (myContext, myShadowOffset, 1, myColor);
+
+ [super drawTextInRect:rect];
+
+ CGColorRelease(myColor);
+ CGColorSpaceRelease(myColorSpace);
+
+ CGContextRestoreGState(myContext);}
+
+@end
View
9 src/FBRequestConnection+Internal.h
@@ -15,12 +15,21 @@
*/
#import "FBRequestConnection.h"
+#import "FBRequestMetadata.h"
+
+@class FBRequestConnectionRetryManager;
@interface FBRequestConnection (Internal)
@property (nonatomic, readonly) BOOL isResultFromCache;
+@property (nonatomic, readonly) NSMutableArray *requests;
+@property (nonatomic, readonly) FBRequestConnectionRetryManager *retryManager;
+
+- (id)initWithMetadata:(NSArray *)metadataArray;
- (void)startWithCacheIdentity:(NSString*)cacheIdentity
skipRoundtripIfCached:(BOOL)consultCache;
+- (FBRequestMetadata *) getRequestMetadata:(FBRequest *)request;
+
@end
View
60 src/FBRequestConnection.h
@@ -24,6 +24,52 @@
@class FBSession;
@class UIImage;
+
+/*!
+ @attribute beta true
+
+ @typedef FBRequestConnectionErrorBehavior enum
+
+ @abstract Describes what automatic error handling behaviors to provide (if any).
+
+ @discussion This is a bitflag enum that can be composed of different values.
+
+ See FBError.h and FBErrorUtility.h for error category and user message details.
+ */
+typedef enum {
+ /*! The default behavior of none */
+ FBRequestConnectionErrorBehaviorNone = 0,
+
+ /*! This will retry any requests whose error category is classified as `FBErrorCategoryRetry`.
+ If the retry fails, the normal handler is invoked. */
+ FBRequestConnectionErrorBehaviorRetry = 1,
+
+ /*! This will automatically surface any SDK provided userMessage (at most one), after
+ retry attempts, but before any reconnects are tried. The alert will have one button
+ whose text can be localized with the key "FBE:AlertMessageButton".
+
+ You should not display your own alert views in your request handler when specifying this
+ behavior.
+ */
+ FBRequestConnectionErrorBehaviorAlertUser = 2,
+
+ /*! This will automatically reconnect a session if the request failed due to an invalid token
+ that would otherwise close the session (such as an expired token or password change). Note
+ this will NOT reconnect a session if the user had uninstalled the app, or if the user had
+ disabled the app's slider in their privacy settings (in cases of iOS 6 system auth).
+ If the session is reconnected, this will transition the session state to FBSessionStateTokenExtended
+ which will invoke any state change handlers. Otherwise, the session is closed as normal.
+
+ This behavior should not be used if the FBRequestConnection contains multiple
+ session instances. Further, when this behavior is used, you must not request new permissions
+ for the session until the connection is completed.
+
+ Lastly, you should avoid using additional FBRequestConnections with the same session because
+ that will be subject to race conditions.
+ */
+ FBRequestConnectionErrorBehaviorReconnectSession = 4,
+} FBRequestConnectionErrorBehavior;
+
/*!
Normally requests return JSON data that is parsed into a set of `NSDictionary`
and `NSArray` objects.
@@ -136,6 +182,20 @@ typedef void (^FBRequestHandler)(FBRequestConnection *connection,
@property(nonatomic, retain, readonly) NSHTTPURLResponse *urlResponse;
/*!
+ @attribute beta true
+
+ @abstract Set the automatic error handling behaviors.
+ @discussion
+
+ This must be set before any requests are added.
+
+ When using retry behaviors, note the FBRequestConnection instance
+ passed to the FBRequestHandler may be a different instance that the
+ one the requests were originally started on.
+*/
+@property (nonatomic, assign) FBRequestConnectionErrorBehavior errorBehavior;
+
+/*!
@methodgroup Adding requests
*/
View
205 src/FBRequestConnection.m
@@ -24,6 +24,8 @@
#import "FBSettings.h"
#import "FBRequestConnection.h"
#import "FBRequestConnection+Internal.h"
+#import "FBRequestConnectionRetryManager.h"
+#import "FBRequestHandlerFactory.h"
#import "FBRequest+Internal.h"
#import "Facebook.h"
#import "FBGraphObject.h"
@@ -36,6 +38,7 @@
// URL construction constants
NSString *const kGraphURLPrefix = @"https://graph.";
+NSString *const kGraphVideoURLPrefix = @"https://graph-video.";
NSString *const kApiURLPrefix = @"https://api.";
NSString *const kBatchKey = @"batch";
NSString *const kBatchMethodKey = @"method";
@@ -62,68 +65,6 @@
typedef void (^KeyValueActionHandler)(NSString *key, id value);
// ----------------------------------------------------------------------------
-// Private class to store requests and their metadata.
-//
-@interface FBRequestMetadata : NSObject
-
-@property (nonatomic, retain) FBRequest *request;
-@property (nonatomic, copy) FBRequestHandler completionHandler;
-@property (nonatomic, copy) NSString *batchEntryName;
-
-- (id) initWithRequest:(FBRequest *)request
- completionHandler:(FBRequestHandler)handler
- batchEntryName:(NSString *)name;
-
-- (void)invokeCompletionHandlerForConnection:(FBRequestConnection *)connection
- withResults:(id)results
- error:(NSError *)error;
-@end
-
-@implementation FBRequestMetadata
-
-@synthesize batchEntryName = _batchEntryName;
-@synthesize completionHandler = _completionHandler;
-@synthesize request = _request;
-
-- (id) initWithRequest:(FBRequest *)request
- completionHandler:(FBRequestHandler)handler
- batchEntryName:(NSString *)name {
-
- if (self = [super init]) {
- self.request = request;
- self.completionHandler = handler;
- self.batchEntryName = name;
- }
- return self;
-}
-
-- (void) dealloc {
- [_request release];
- [_completionHandler release];
- [_batchEntryName release];
- [super dealloc];
-}
-
-- (void)invokeCompletionHandlerForConnection:(FBRequestConnection *)connection
- withResults:(id)results
- error:(NSError *)error {
- if (self.completionHandler) {
- self.completionHandler(connection, results, error);
- }
-}
-
-- (NSString*)description {
- return [NSString stringWithFormat:@"<%@: %p, batchEntryName: %@, completionHandler: %p, request: %@>",
- NSStringFromClass([self class]),
- self,
- self.batchEntryName,
- self.completionHandler,
- self.request.description];
-}
-
-@end
-
-// ----------------------------------------------------------------------------
// FBRequestConnectionState
typedef enum FBRequestConnectionState {
@@ -137,7 +78,9 @@ - (NSString*)description {
// ----------------------------------------------------------------------------
// Private properties and methods
-@interface FBRequestConnection ()
+@interface FBRequestConnection () {
+ BOOL _errorBehavior;
+}
@property (nonatomic, retain) FBURLConnection *connection;
@property (nonatomic, retain) NSMutableArray *requests;
@@ -149,6 +92,7 @@ @interface FBRequestConnection ()
@property (nonatomic, retain) FBLogger *logger;
@property (nonatomic) unsigned long requestStartTime;
@property (nonatomic, readonly) BOOL isResultFromCache;
+@property (nonatomic, retain) FBRequestConnectionRetryManager *retryManager;
- (NSMutableURLRequest *)requestWithBatch:(NSArray *)requests
timeout:(NSTimeInterval)timeout;
@@ -234,17 +178,6 @@ @implementation FBRequestConnection
// ----------------------------------------------------------------------------
// Property implementations
-@synthesize connection = _connection;
-@synthesize requests = _requests;
-@synthesize state = _state;
-@synthesize timeout = _timeout;
-@synthesize internalUrlRequest = _internalUrlRequest;
-@synthesize urlResponse = _urlResponse;
-@synthesize deprecatedRequest = _deprecatedRequest;
-@synthesize logger = _logger;
-@synthesize requestStartTime = _requestStartTime;
-@synthesize isResultFromCache = _isResultFromCache;
-
- (NSMutableURLRequest *)urlRequest
{
if (self.internalUrlRequest) {
@@ -271,6 +204,17 @@ - (void)setUrlRequest:(NSMutableURLRequest *)request
self.internalUrlRequest = request;
}
+- (FBRequestConnectionErrorBehavior)errorBehavior
+{
+ return _errorBehavior;
+}
+
+- (void)setErrorBehavior:(FBRequestConnectionErrorBehavior)errorBehavior
+{
+ NSAssert(self.requests.count == 0, @"Cannot set errorBehavior after requests have been added");
+ _errorBehavior = errorBehavior;
+}
+
// ----------------------------------------------------------------------------
// Lifetime
@@ -279,6 +223,7 @@ - (id)init
return [self initWithTimeout:kDefaultTimeout];
}
+// designated initializer
- (id)initWithTimeout:(NSTimeInterval)timeout
{
if (self = [super init]) {
@@ -291,6 +236,16 @@ - (id)initWithTimeout:(NSTimeInterval)timeout
return self;
}
+// internal constructor used for initializing with existing metadata/fbrequest instances,
+// ostensibly for the retry flow.
+- (id)initWithMetadata:(NSArray *)metadataArray
+{
+ if (self = [self initWithTimeout:kDefaultTimeout]) {
+ self.requests = [[metadataArray mutableCopy] autorelease];
+ }
+ return self;
+}
+
- (void)dealloc
{
[_connection cancel];
@@ -300,12 +255,13 @@ - (void)dealloc
[_urlResponse release];
[_deprecatedRequest release];
[_logger release];
+ [_retryManager release];
+
[super dealloc];
}
// ----------------------------------------------------------------------------
// Public methods
-
- (void)addRequest:(FBRequest *)request
completionHandler:(FBRequestHandler)handler
{
@@ -316,19 +272,29 @@ - (void)addRequest:(FBRequest *)request
completionHandler:(FBRequestHandler)handler
batchEntryName:(NSString *)name
{
+ [self addRequest:request completionHandler:handler batchEntryName:name behavior:self.errorBehavior];
+}
+
+- (void)addRequest:(FBRequest*)request
+ completionHandler:(FBRequestHandler)handler
+ batchEntryName:(NSString*)name
+ behavior:(FBRequestConnectionErrorBehavior)behavior
+{
NSAssert(self.state == kStateCreated,
@"Requests must be added before starting or cancelling.");
-
+
FBRequestMetadata *metadata = [[FBRequestMetadata alloc] initWithRequest:request
completionHandler:handler
- batchEntryName:name];
+ batchEntryName:name
+ behavior:behavior];
+
[self.requests addObject:metadata];
[metadata release];
}
- (void)start
{
- [self startWithCacheIdentity:nil
+ [self startWithCacheIdentity:nil
skipRoundtripIfCached:NO];
}
@@ -336,10 +302,11 @@ - (void)cancel {
// Cancelling self.connection might trigger error handlers that cause us to
// get freed. Make sure we stick around long enough to finish this method call.
[[self retain] autorelease];
-
+
+ // Set the state to cancelled now prior to any handlers being invoked.
+ self.state = kStateCancelled;
[self.connection cancel];
self.connection = nil;
- self.state = kStateCancelled;
}
// ----------------------------------------------------------------------------
@@ -768,7 +735,17 @@ - (NSString *)urlStringForSingleRequest:(FBRequest *)request forBatch:(BOOL)forB
if (forBatch) {
baseURL = request.graphPath;
} else {
- baseURL = [[FBUtility buildFacebookUrlWithPre:kGraphURLPrefix withPost:@"/"] stringByAppendingString:request.graphPath];
+ NSString *prefix = kGraphURLPrefix;
+ // We special case a graph post to <id>/videos and send it to graph-video.facebook.com
+ // We only do this for non batch post requests
+ if ([[request.HTTPMethod uppercaseString] isEqualToString:@"POST"] &&
+ [[request.graphPath lowercaseString] hasSuffix:@"/videos"]) {
+ NSArray *components = [request.graphPath componentsSeparatedByString:@"/"];
+ if ([components count] == 2) {
+ prefix = kGraphVideoURLPrefix;
+ }
+ }
+ baseURL = [[FBUtility buildFacebookUrlWithPre:prefix withPost:@"/"] stringByAppendingString:request.graphPath];
}
}
@@ -995,10 +972,12 @@ - (void)completeWithResponse:(NSURLResponse *)response
data:(NSData *)data
orError:(NSError *)error
{
- NSAssert(self.state == kStateStarted,
- @"Unexpected state %d in completeWithResponse",
- self.state);
- self.state = kStateCompleted;
+ if (self.state != kStateCancelled) {
+ NSAssert(self.state == kStateStarted,
+ @"Unexpected state %d in completeWithResponse",
+ self.state);
+ self.state = kStateCompleted;
+ }
int statusCode;
if (response) {
@@ -1249,9 +1228,22 @@ - (NSError*) unpackIndividualJSONResponseError:(NSError *)itemError {
return itemError;
}
+
+// Helper method to determine if FBRequestConnection should close
+// the session for a given FBRequest.
+- (BOOL) shouldCloseRequestSession:(FBRequest *)request {
+ // We don't close requests whose session is being repaired
+ // since the repair resolution is now responsible for
+ // either maintaining the session or closing it.
+ return request.canCloseSessionOnError && !request.session.isRepairing;
+}
+
- (void)completeWithResults:(NSArray *)results
orError:(NSError *)error
{
+ // set up a new retry manager for this flow.
+ self.retryManager = [[[FBRequestConnectionRetryManager alloc] initWithFBRequestConnection:self] autorelease];
+
int count = [self.requests count];
for (int i = 0; i < count; i++) {
FBRequestMetadata *metadata = [self.requests objectAtIndex:i];
@@ -1277,10 +1269,13 @@ - (void)completeWithResults:(NSArray *)results
[self isInsufficientPermissionError:error resultIndex:resultIndex]) {
// if we lack permissions, use this as a cue to refresh the
// OS's understanding of current permissions
+ [self.retryManager incrementExpectedPerformRetryCount];
[[FBSystemAccountStoreAdapter sharedInstance]
renewSystemAuthorization:^(ACAccountCredentialRenewResult result, NSError *error) {
[metadata invokeCompletionHandlerForConnection:self withResults:body error:unpackedError];
+ [self.retryManager performRetries];
}];
+
} else if ([self isInvalidSessionError:itemError resultIndex:resultIndex]) {
// For invalid sessions, we also need to close the session before
// invoking the "further logic".
@@ -1290,6 +1285,7 @@ - (void)completeWithResults:(NSArray *)results
&& [FBSystemAccountStoreAdapter sharedInstance].canRequestAccessWithoutUI) {
// If token is expired and iOS says user has granted permissions
// we can simply renew the token and flip the error to a retry.
+ [self.retryManager incrementExpectedPerformRetryCount];
[[FBSystemAccountStoreAdapter sharedInstance]
renewSystemAuthorization:^(ACAccountCredentialRenewResult result, NSError *error) {
if (result == ACAccountCredentialRenewResultRenewed) {
@@ -1306,18 +1302,20 @@ - (void)completeWithResults:(NSArray *)results
// This shouldn't happen but if the request fails,
// revert to the original flow of closing session
// and surfacing the original error.
- if (metadata.request.canCloseSessionOnError) {
+ if ([self shouldCloseRequestSession:metadata.request]) {
[metadata.request.session closeAndClearTokenInformation:unpackedError];
}
[metadata invokeCompletionHandlerForConnection:self withResults:body error:unpackedError];
}
+ [self.retryManager performRetries];
}
];
} else {
- if (metadata.request.canCloseSessionOnError) {
+ if ([self shouldCloseRequestSession:metadata.request]) {
[metadata.request.session closeAndClearTokenInformation:unpackedError];
}
[metadata invokeCompletionHandlerForConnection:self withResults:body error:unpackedError];
+ [self.retryManager performRetries];
}
}];
} else if ([self isPasswordChangeError:itemError resultIndex:resultIndex]) {
@@ -1327,21 +1325,25 @@ - (void)completeWithResults:(NSArray *)results
// with an old token which would immediately be closed, we tell our adapter
// that we want to force a blocking renew until success.
[FBSystemAccountStoreAdapter sharedInstance].forceBlockingRenew = YES;
- if (metadata.request.canCloseSessionOnError) {
+ if ([self shouldCloseRequestSession:metadata.request]) {
[metadata.request.session closeAndClearTokenInformation:unpackedError];
}
[metadata invokeCompletionHandlerForConnection:self withResults:body error:unpackedError];
} else {
// For other invalid session cases, we can simply issue the renew now
// to update the system account's world view.
+ [self.retryManager incrementExpectedPerformRetryCount];
[[FBSystemAccountStoreAdapter sharedInstance]
renewSystemAuthorization:^(ACAccountCredentialRenewResult result, NSError *error) {
- [metadata.request.session closeAndClearTokenInformation:unpackedError];
+ if ([self shouldCloseRequestSession:metadata.request]) {
+ [metadata.request.session closeAndClearTokenInformation:unpackedError];
+ }
[metadata invokeCompletionHandlerForConnection:self withResults:body error:unpackedError];
+ [self.retryManager performRetries];
}];
}
} else {
- if (metadata.request.canCloseSessionOnError) {
+ if ([self shouldCloseRequestSession:metadata.request]) {
[metadata.request.session closeAndClearTokenInformation:unpackedError];
}
[metadata invokeCompletionHandlerForConnection:self withResults:body error:unpackedError];
@@ -1359,6 +1361,13 @@ - (void)completeWithResults:(NSArray *)results
[metadata invokeCompletionHandlerForConnection:self withResults:body error:unpackedError];
}
}
+
+ // NOTE: if you are attempting to add more logic that should be executed after processing
+ // the results, you will also need to the same logic to any area above that calls
+ // invokeCompletionHandlerForConnection inside a block. Until we refactor to a better async framework,
+ // this is necessary to chain work after the ios 6 calls.
+
+ [self.retryManager performRetries];
}
- (NSError *)errorFromResult:(id)idResult
@@ -1471,6 +1480,10 @@ - (BOOL)isInsufficientPermissionError:(NSError *)error
- (BOOL)isInvalidSessionError:(NSError *)error
resultIndex:(int)index {
+ // Please note the retry behaviors in FBRequestHandlerFactory are coupled
+ // to the FBRequestConnection invalid session behavior, so any changes
+ // to conditions that trigger `closeAndClearTokenInformation` will probably
+ // need to replicate to the FBRequestHandlerFactory.
int code = 0, subcode = 0;
[FBErrorUtility fberrorGetCodeValueForError:error
index:index
@@ -1592,6 +1605,16 @@ + (void)addRequestToExtendTokenForSession:(FBSession*)session connection:(FBRequ
[request release];
}
+// Helper method to map a request to its metadata instance.
+- (FBRequestMetadata *) getRequestMetadata:(FBRequest *)request {
+ for (FBRequestMetadata *metadata in self.requests) {
+ if (metadata.request == request) {
+ return metadata;
+ }
+ }
+ return nil;
+}
+
#pragma mark Debugging helpers
- (NSString*)description {
@@ -1610,7 +1633,7 @@ - (NSString*)description {
}
[result appendString:@"\n)>"];
return result;
-
+
}
#pragma mark -
View
80 src/FBRequestConnectionRetryManager.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2010-present 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 "FBRequestConnection.h"
+#import "FBRequestMetadata.h"
+#import "FBSession.h"
+
+typedef enum {
+ // The normal retry state where we will perform retries.
+ FBRequestConnectionRetryManagerStateNormal,
+
+ // Indicates retries are aborted, so the user supplied handlers should be invoked.
+ FBRequestConnectionRetryManagerStateAbortRetries,
+
+ // Indicates we are going to repair the session, which implies that retries are aborted
+ // and supplied handlers are NOT invoked since they will be evaluated after the
+ // repair operation is executed.
+ FBRequestConnectionRetryManagerStateRepairSession
+} FBRequestConnectionRetryManagerState;
+
+// Internal class for tracking retries for a given FBRequestConnection
+// Essentially this helps FBRequestConnection support a two phase approach
+// to processing its request handlers. The first pass is the normal
+// loop over the request handlers. The handlers then have the opportunity
+// to passage messages to this RetryManager (typically `addRequestMetadata`
+// to queue a request for the second phase, or update the state for more complex
+// scenarios like repairing a session).
+// Then the second phase executes and will eventually (possibly after an
+// attempt to repair the session) invoke the queued handlers.
+// Thus, this class has the unfortunate responsibility of keeping state
+// between handlers.
+@interface FBRequestConnectionRetryManager : NSObject
+
+// This is like a delegate pattern in that this is a weak reference to the
+// "parent" FBRequestConnection since we expect the parent to have a strong
+// reference to this RetryManager instance.
+@property (nonatomic, unsafe_unretained) FBRequestConnection *requestConnection;
+
+// See above enum.
+@property (nonatomic, assign) FBRequestConnectionRetryManagerState state;
+
+// It's possible for a batch of FBRequests to use different session instances.
+// For now, we only support reconnecting one session instance (especially since
+// the UX would probably be broken to login twice). This property tracks the
+// session that has been identified for reconnecting and is assigned at runtime
+// when the first FBRequest with the reconnecting behavior is encountered
+@property (nonatomic, retain) FBSession* sessionToReconnect;
+
+// A message that can be shown to the user before executing the retry batch.
+@property (nonatomic, copy) NSString* alertMessage;
+
+-(id) initWithFBRequestConnection:(FBRequestConnection *)requestConnection;
+
+// The main method to invoke the retry batch; it also checks alertMessage
+// to possibly present an alertview.
+-(void) performRetries;
+
+// Add a request to the retry batch.
+-(void) addRequestMetadata:(FBRequestMetadata *)metadata;
+
+// A hack to deal with the async callbacks in FBRequestConnection,
+// specifically this acts as a semaphore-like counter to make sure
+// performRetries is invoked logically once and at the right time. See
+// that method's implementation for more details.
+-(void) incrementExpectedPerformRetryCount;
+
+@end
View
197 src/FBRequestConnectionRetryManager.m
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2010-present 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/NSThread.h>
+
+#import "FBRequestConnectionRetryManager.h"
+#import "FBRequestConnection+Internal.h"
+#import "FBRequest+Internal.h"
+#import "FBSession+Internal.h"
+#import "FBUtility.h"
+
+// An INTERNAL "light-weight" structure for presenting an alertview and assigning a completion block to call after
+// the alert has been dismissed. The alert will be dispatched to the main queue. The callback will also be dispatched
+// to the the main thread after the alert has been dismissed.
+@interface FBRequestConnectionRetryManagerAlertViewHelper : NSObject<UIAlertViewDelegate>
+
+-(void) show:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle
+ handler:(void(^)(void)) callback;
+
+@end
+
+@interface FBRequestConnectionRetryManagerAlertViewHelper()
+
+@property (nonatomic, copy) void(^callback)(void);
+
+@end
+
+@implementation FBRequestConnectionRetryManagerAlertViewHelper
+
+// Note this may require refactoring if you plan on presenting multiple dialogs.
+-(void) show:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle
+ handler:(void(^)(void)) callback {
+
+ self.callback = callback;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [[[UIAlertView alloc] initWithTitle:title message:message delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil] show];
+ });
+}
+
+-(void) alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
+ if (self.callback) {
+ dispatch_async(dispatch_get_main_queue(), self.callback);
+ }
+}
+
+-(void) dealloc {
+ self.callback = nil;
+
+ [super dealloc];
+}
+@end
+
+@interface FBRequestConnectionRetryManager()
+
+@property (nonatomic, retain) NSMutableArray *requestMetadatas;
+@property (nonatomic, retain) FBRequestConnectionRetryManagerAlertViewHelper *alertViewHelper;
+@property (atomic, assign) int expectedPerformRetryCount;
+
+@end
+
+
+@implementation FBRequestConnectionRetryManager
+
+-(id) initWithFBRequestConnection:(FBRequestConnection *)requestConnection {
+ if (self = [super init]){
+ self.requestConnection = requestConnection;
+ _requestMetadatas = [[NSMutableArray alloc] init];
+ _alertViewHelper = [[FBRequestConnectionRetryManagerAlertViewHelper alloc] init];
+ }
+ return self;
+}
+
+-(void) addRequestMetadata:(FBRequestMetadata *)metadata {
+ [self.requestMetadatas addObject:metadata];
+}
+
+-(void) performRetries {
+ if (self.expectedPerformRetryCount > 0) {
+ // As noted in `expectedPerformRetryCount` declaration, this condition
+ // is to help deal with the async callbacks in FBRequestConnection. Specifically,
+ // the async ios 6 calls need to be processed before any attempt at performRetries.
+ // So before issuing the async calls, we increment the counter so that at the end
+ // of the callback, we can call performRetries. The performRetries will no-op
+ // if the counter is still positive; otherwise it decrements the counter. This
+ // allows the "last" performRetries invocation to actually do its work (since
+ // there a performRetries call at the end of FBRequestConnection completeWithResults
+ // that is _not_ paired with a counter increment).
+ // Note this is still not 100% thread-safe but since all the counter increments
+ // happen beforehand and on the same thread (the completeWithResults loop), it
+ // should be fine albeit fragile until we refactor the async callbacks.
+ self.expectedPerformRetryCount--;
+ return;
+ }
+
+ if (self.alertMessage.length > 0) {
+ [_requestConnection retain];
+ NSString *buttonText = [FBUtility localizedStringForKey:@"FBE:AlertMessageButton" withDefault:@"OK"];
+ [self.alertViewHelper show:nil message:self.alertMessage cancelButtonTitle:buttonText
+ handler:^{
+ self.alertMessage = nil;
+ [self performRetries];
+ [_requestConnection release];
+ }];
+ return;
+ }
+
+ if (self.requestMetadatas.count > 0) {
+ switch (self.state) {
+ case FBRequestConnectionRetryManagerStateNormal : {
+ FBRequestConnection *connectionToRetry = [[[FBRequestConnection alloc] initWithMetadata:self.requestMetadatas] autorelease];
+ [connectionToRetry start];
+ break;
+ }
+ case FBRequestConnectionRetryManagerStateAbortRetries : {
+ for (FBRequestMetadata *metadata in self.requestMetadatas) {
+ [metadata invokeCompletionHandlerForConnection:self.requestConnection withResults:metadata.originalResult error:metadata.originalError];
+ }
+ break;
+ }
+ case FBRequestConnectionRetryManagerStateRepairSession : {
+ [_requestConnection retain];
+ NSThread *thread = self.sessionToReconnect.affinitizedThread ?: [NSThread mainThread];
+ FBSessionRequestPermissionResultHandler handler = [[^(FBSession *session, NSError *sessionError) {
+ if (session.isOpen && !sessionError) {
+ [self repairSuccess];
+ } else {
+ [self repairFailed];
+ }
+ [_requestConnection release];
+ } copy] autorelease];
+
+ [self.sessionToReconnect performSelector:@selector(repairWithHandler:) onThread:thread withObject:handler waitUntilDone:NO];
+
+ break;
+ }
+ }
+ }
+}
+
+-(void) repairSuccess {
+ if (self.requestMetadatas.count > 0) {
+ // Construct new request connection and re-add the requests, but removing
+ // the "autoreconnect" behavior (though we still allow the simpler retry)
+ // and alerts (since those would have already been surfaced prior to the repair attempt).
+ FBRequestConnection *connectionToRetry = [[[FBRequestConnection alloc] init] autorelease];
+ connectionToRetry.errorBehavior = self.requestConnection.errorBehavior
+ & ~FBRequestConnectionErrorBehaviorReconnectSession
+ & ~FBRequestConnectionErrorBehaviorAlertUser;
+ for (FBRequestMetadata *metadata in self.requestMetadatas) {
+ metadata.request.canCloseSessionOnError = YES;
+ [connectionToRetry addRequest:metadata.request
+ completionHandler:metadata.originalCompletionHandler
+ batchEntryName:metadata.batchEntryName];
+ }
+ [connectionToRetry start];
+ }
+}
+
+-(void) repairFailed {
+ if (self.requestMetadatas.count > 0) {
+ for (FBRequestMetadata *metadata in self.requestMetadatas) {
+ // Since we were unable to repair the session, we will close it now since that is the existing behavior for
+ // errors that would have caused a repair attempt.
+ if (metadata.request.session.isOpen && !metadata.request.session.isRepairing) {
+ [metadata.request.session closeAndClearTokenInformation:metadata.originalError];
+ }
+ metadata.originalCompletionHandler(self.requestConnection, metadata.originalResult, metadata.originalError);
+ }
+ }
+}
+
+-(void) incrementExpectedPerformRetryCount {
+ self.expectedPerformRetryCount++;
+}
+
+-(void) dealloc {
+ self.sessionToReconnect = nil;
+ self.alertMessage = nil;
+ self.requestMetadatas = nil;
+ self.alertViewHelper = nil;
+
+ [super dealloc];
+}
+@end
View
18 src/FBRequestHandlerFactory.h
@@ -0,0 +1,18 @@
+
+#import "FBRequestConnection.h"
+
+// Internal only factory class to curry FBRequestHandlers to provide various
+// error handling behaviors. See `FBRequestConnection.errorBehavior`
+// and `FBRequestConnectionRetryManager` for details.
+
+// Essentially this currying approach offers the flexibility of chaining work internally while
+// maintaining the existing surface area of request handlers. In the future this could easily
+// be replaced by an actual Promises/Deferred framework (or even provide a responder object param
+// to the FBRequestHandler callback for even more extensibility)
+@interface FBRequestHandlerFactory : NSObject
+
++(FBRequestHandler) handlerThatRetries:(FBRequestHandler )handler forRequest:(FBRequest* )request;
++(FBRequestHandler) handlerThatReconnects:(FBRequestHandler )handler forRequest:(FBRequest* )request;
++(FBRequestHandler) handlerThatAlertsUser:(FBRequestHandler )handler forRequest:(FBRequest* )request;
+
+@end
View
146 src/FBRequestHandlerFactory.m
@@ -0,0 +1,146 @@
+
+#import <UIKit/UIKit.h>
+#import "FBAccessTokenData.h"
+#import "FBError.h"
+#import "FBErrorUtility+Internal.h"
+#import "FBRequest+Internal.h"
+#import "FBRequestConnection+Internal.h"
+#import "FBRequestConnectionRetryManager.h"
+#import "FBRequestHandlerFactory.h"
+#import "FBRequestMetadata.h"
+#import "FBSession+Internal.h"
+#import "FBSystemAccountStoreAdapter.h"
+
+@implementation FBRequestHandlerFactory
+
+// These handlers should generally conform to the following pattern:
+// 1. Save any original errors/results to the metadata.
+// 2. Check the retryManager.state to determine if retry behavior should be aborted.
+// 3. Invoking the original handler if the retry condition is not met.
+// We (ab)use the retryManager to maintain any necessary state between handlers
+// (such as an optional user facing alert message).
++(FBRequestHandler) handlerThatRetries:(FBRequestHandler )handler forRequest:(FBRequest* )request {
+ return [[^(FBRequestConnection *connection,
+ id result,
+ NSError *error){
+ FBRequestMetadata *metadata = [connection getRequestMetadata:request];
+ metadata.originalError = metadata.originalError ?: error;
+ metadata.originalResult = metadata.originalResult ?: result;
+
+ if (connection.retryManager.state != FBRequestConnectionRetryManagerStateAbortRetries
+ && error
+ && [FBErrorUtility errorCategoryForError:error] == FBErrorCategoryRetry) {
+
+ if (metadata.retryCount < FBREQUEST_DEFAULT_MAX_RETRY_LIMIT) {
+ metadata.retryCount++;
+ [connection.retryManager addRequestMetadata:metadata];
+ return;
+ }
+ }
+
+ // Otherwise, invoke the supplied handler
+ if (handler){
+ handler(connection, result, error);
+ }
+ } copy] autorelease];
+}
+
++(FBRequestHandler) handlerThatAlertsUser:(FBRequestHandler )handler forRequest:(FBRequest* )request {
+ return [[^(FBRequestConnection *connection,
+ id result,
+ NSError *error){
+ FBRequestMetadata *metadata = [connection getRequestMetadata:request];
+ metadata.originalError = metadata.originalError ?: error;
+ metadata.originalResult = metadata.originalResult ?: result;
+ NSString *message = [FBErrorUtility userMessageForError:error];
+ if (connection.retryManager.state != FBRequestConnectionRetryManagerStateAbortRetries
+ && message.length > 0) {
+
+ connection.retryManager.alertMessage = message;
+ }
+
+ // In this case, always invoke the handler.
+ if (handler) {
+ handler(connection, result, error);
+ }
+
+ } copy] autorelease];
+}
+
++(FBRequestHandler) handlerThatReconnects:(FBRequestHandler )handler forRequest:(FBRequest* )request {
+ // Defer closing of sessions for these kinds of requests.
+ request.canCloseSessionOnError = NO;
+ return [[^(FBRequestConnection *connection,
+ id result,
+ NSError *error){
+ FBRequestMetadata *metadata = [connection getRequestMetadata:request];
+ metadata.originalError = metadata.originalError ?: error;
+ metadata.originalResult = metadata.originalResult ?: result;
+
+ FBErrorCategory errorCategory = error ? [FBErrorUtility errorCategoryForError:error] : FBErrorCategoryInvalid;
+ if (connection.retryManager.state != FBRequestConnectionRetryManagerStateAbortRetries
+ && error
+ && errorCategory == FBErrorCategoryAuthenticationReopenSession){
+ int code, subcode;
+ [FBErrorUtility fberrorGetCodeValueForError:error
+ index:0
+ code:&code
+ subcode:&subcode];
+
+ // If the session has already been closed, we cannot repair.
+ BOOL canRepair = request.session.isOpen;
+ switch (subcode) {
+ case FBAuthSubcodeAppNotInstalled :
+ case FBAuthSubcodeUnconfirmedUser : canRepair = NO; break;
+ }
+
+ if (canRepair) {
+ if (connection.retryManager.sessionToReconnect == nil) {
+ connection.retryManager.sessionToReconnect = request.session;
+ }
+
+ if (request.session.accessTokenData.loginType == FBSessionLoginTypeSystemAccount) {
+ // For iOS 6, we also cannot reconnect disabled app sliders.
+ // This has the side effect of not repairing sessions on a device
+ // that has since removed the Facebook device account since we cannot distinguish
+ // between a disabled slider versus no account set up (in the former, we do not
+ // want to attempt FB App/Safari SSO).
+ canRepair = [FBSystemAccountStoreAdapter sharedInstance].canRequestAccessWithoutUI;
+ }
+
+ if (canRepair) {
+ if (connection.retryManager.sessionToReconnect == nil) {
+ connection.retryManager.sessionToReconnect = request.session;
+ }
+
+ // Only support reconnecting one session instance for a give request connection.
+ if (connection.retryManager.sessionToReconnect == request.session) {
+
+ connection.retryManager.sessionToReconnect = request.session;
+ [connection.retryManager addRequestMetadata:metadata];
+
+ connection.retryManager.state = FBRequestConnectionRetryManagerStateRepairSession;
+ return;
+ }
+ }
+ }
+ }
+
+ // Otherwise, invoke the supplied handler
+ if (handler){
+ // Since FBRequestConnection typically closes invalid sessions before invoking the supplied handler,
+ // we have to manually mimic that behavior here.
+ request.canCloseSessionOnError = YES;
+ if (errorCategory == FBErrorCategoryAuthenticationReopenSession){
+ [request.session closeAndClearTokenInformation:error];
+ }
+
+ handler(connection, result, error);
+ }
+
+ } copy] autorelease];
+}
+
+@end
+
+
View
45 src/FBRequestMetadata.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2010-present 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 "FBRequestConnection.h"
+
+// Defines the maximum number of retries for the FBRequestConnectionErrorBehaviorRetry.
+extern const int FBREQUEST_DEFAULT_MAX_RETRY_LIMIT;
+
+// Internal only class to facilitate FBRequest processing, specifically
+// associating FBRequest and FBRequestHandler instances and necessary
+// data for retry processing.
+@interface FBRequestMetadata : NSObject
+
+@property (nonatomic, retain) FBRequest *request;
+@property (nonatomic, copy) FBRequestHandler completionHandler;
+@property (nonatomic, copy) NSString *batchEntryName;
+@property (nonatomic, assign) FBRequestConnectionErrorBehavior behavior;
+@property (nonatomic, copy) FBRequestHandler originalCompletionHandler;
+
+@property (nonatomic, assign) int retryCount;
+@property (nonatomic, retain) id originalResult;
+@property (nonatomic, retain) NSError* originalError;
+
+- (id) initWithRequest:(FBRequest *)request
+ completionHandler:(FBRequestHandler)handler
+ batchEntryName:(NSString *)name
+ behavior:(FBRequestConnectionErrorBehavior) behavior;
+
+- (void)invokeCompletionHandlerForConnection:(FBRequestConnection *)connection
+ withResults:(id)results
+ error:(NSError *)error;
+@end
View
82 src/FBRequestMetadata.m
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2010-present 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