Skip to content
This repository
Browse code

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
Jason Clark authored February 09, 2012
5  .arcconfig
... ...
@@ -0,0 +1,5 @@
  1
+{
  2
+  "project_id" : "facebook-ios-sdk",
  3
+  "conduit_uri" : "https://phabricator.fb.com/api/",
  4
+  "copyright_holder" : "Facebook"
  5
+}
1  .gitignore
@@ -21,7 +21,6 @@ test/UnitTest/UnitTest.xcodeproj/*.mode*
21 21
 test/UnitTest/build/
22 22
 *~
23 23
 *#
24  
-.arcconfig
25 24
 .DS_Store
26 25
 project.xcworkspace
27 26
 xcuserdata
6  sample/Hackbook/Hackbook.xcodeproj/project.pbxproj
@@ -38,6 +38,7 @@
38 38
 		30EA73FC13F5D590003DC0D2 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30EA73FB13F5D590003DC0D2 /* CoreLocation.framework */; };
39 39
 		30ED588C14358F8A00A226C3 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 30ED588B14358F8A00A226C3 /* Default@2x.png */; };
40 40
 		30ED588F14358F9400A226C3 /* Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 30ED588E14358F9400A226C3 /* Icon@2x.png */; };
  41
+		84E7D99614D9CC13006A6299 /* FBFrictionlessRequestSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 84E7D99514D9CC13006A6299 /* FBFrictionlessRequestSettings.m */; };
41 42
 /* End PBXBuildFile section */
42 43
 
43 44
 /* Begin PBXFileReference section */
@@ -92,6 +93,8 @@
92 93
 		30EA73FB13F5D590003DC0D2 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; };
93 94
 		30ED588B14358F8A00A226C3 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = "<group>"; };
94 95
 		30ED588E14358F9400A226C3 /* Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon@2x.png"; sourceTree = "<group>"; };
  96
+		84E7D99414D9CC13006A6299 /* FBFrictionlessRequestSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FBFrictionlessRequestSettings.h; path = ../../src/FBFrictionlessRequestSettings.h; sourceTree = "<group>"; };
  97
+		84E7D99514D9CC13006A6299 /* FBFrictionlessRequestSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FBFrictionlessRequestSettings.m; path = ../../src/FBFrictionlessRequestSettings.m; sourceTree = "<group>"; };
95 98
 /* End PBXFileReference section */
96 99
 
97 100
 /* Begin PBXFrameworksBuildPhase section */
@@ -197,6 +200,8 @@
197 200
 				30EA73D613F5D523003DC0D2 /* FBLoginDialog.m */,
198 201
 				30EA73D713F5D523003DC0D2 /* FBRequest.h */,
199 202
 				30EA73D813F5D523003DC0D2 /* FBRequest.m */,
  203
+				84E7D99414D9CC13006A6299 /* FBFrictionlessRequestSettings.h */,
  204
+				84E7D99514D9CC13006A6299 /* FBFrictionlessRequestSettings.m */,
200 205
 				30EA73D913F5D523003DC0D2 /* JSON */,
201 206
 			);
202 207
 			name = FBConnect;
@@ -310,6 +315,7 @@
310 315
 				30EA73F013F5D523003DC0D2 /* SBJsonBase.m in Sources */,
311 316
 				30EA73F113F5D523003DC0D2 /* SBJsonParser.m in Sources */,
312 317
 				30EA73F213F5D523003DC0D2 /* SBJsonWriter.m in Sources */,
  318
+				84E7D99614D9CC13006A6299 /* FBFrictionlessRequestSettings.m in Sources */,
313 319
 			);
314 320
 			runOnlyForDeploymentPostprocessing = 0;
315 321
 		};
46  sample/Hackbook/Hackbook/APICallsViewController.m
@@ -540,6 +540,28 @@ - (void)getUserFriendTargetDialogRequest {
540 540
 }
541 541
 
542 542
 /*
  543
+ * API: Enable frictionless in the SDK, retrieve friends enabled for frictionless send
  544
+ */
  545
+- (void)enableFrictionlessAppRequests {
  546
+    HackbookAppDelegate *delegate = 
  547
+        (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
  548
+    
  549
+    // Enable frictionless app requests
  550
+    [[delegate facebook] enableFrictionlessRequests];
  551
+    
  552
+    UIAlertView *alertView = [[UIAlertView alloc]
  553
+                              initWithTitle:@"Enabled Frictionless Requests"
  554
+                              message:@"Request actions such as\n"
  555
+                                      @"Send Request and Send Invite\n"
  556
+                                      @"now support frictionless behavior."
  557
+                              delegate:self
  558
+                              cancelButtonTitle:@"OK"
  559
+                              otherButtonTitles:nil,
  560
+                              nil];
  561
+    [alertView show];
  562
+}
  563
+
  564
+/*
543 565
  * --------------------------------------------------------------------------
544 566
  * Graph API
545 567
  * --------------------------------------------------------------------------
@@ -939,12 +961,13 @@ - (void)request:(FBRequest *)request didLoad:(id)result {
939 961
         }
940 962
         case kAPIFriendsForDialogFeed:
941 963
         {
942  
-            NSArray *resultData = [result objectForKey:@"data"];
  964
+            NSArray *resultData = [result objectForKey: @"data"];
943 965
             // Check that the user has friends
944 966
             if ([resultData count] > 0) {
945 967
                 // Pick a random friend to post the feed to
946 968
                 int randomNumber = arc4random() % [resultData count];
947  
-                [self apiDialogFeedFriend:[[resultData objectAtIndex:randomNumber] objectForKey:@"id"]];
  969
+                [self apiDialogFeedFriend: 
  970
+                    [[resultData objectAtIndex: randomNumber] objectForKey: @"id"]];
948 971
             } else {
949 972
                 [self showMessage:@"You do not have any friends to post to."];
950 973
             }
@@ -1015,17 +1038,24 @@ - (void)request:(FBRequest *)request didLoad:(id)result {
1015 1038
         }
1016 1039
         case kAPIFriendsForTargetDialogRequests:
1017 1040
         {
1018  
-            NSArray *resultData = [result objectForKey:@"data"];
1019  
-            if ([resultData count] > 0) {
1020  
-                [self apiDialogRequestsSendTarget:[[resultData objectAtIndex:0] objectForKey:@"id"]];
  1041
+            NSArray *resultData = [result objectForKey: @"data"];
  1042
+            // got friends?
  1043
+            if ([resultData count] > 0) { 
  1044
+                // pick a random one to send a request to
  1045
+                int randomIndex = arc4random() % [resultData count];	
  1046
+                NSString* randomFriend = 
  1047
+                    [[resultData objectAtIndex: randomIndex] objectForKey: @"id"];
  1048
+                [self apiDialogRequestsSendTarget:randomFriend];
1021 1049
             } else {
1022  
-                [self showMessage:@"You have no friends to select."];
  1050
+                [self showMessage: @"You have no friends to select."];
1023 1051
             }
1024 1052
             break;
1025 1053
         }
1026 1054
         case kAPIGraphMe:
1027 1055
         {
1028  
-            NSString *nameID = [[NSString alloc] initWithFormat:@"%@ (%@)", [result objectForKey:@"name"], [result objectForKey:@"id"]];
  1056
+            NSString *nameID = [[NSString alloc] initWithFormat: @"%@ (%@)", 
  1057
+                                [result objectForKey:@"name"], 
  1058
+                                [result objectForKey:@"id"]];
1029 1059
             NSMutableArray *userData = [[NSMutableArray alloc] initWithObjects:
1030 1060
                                         [NSDictionary dictionaryWithObjectsAndKeys:
1031 1061
                                          [result objectForKey:@"id"], @"id",
@@ -1162,7 +1192,7 @@ - (void)dialogCompleteWithUrl:(NSURL *)url {
1162 1192
             // Successful requests return one or more request_ids.
1163 1193
             // Get any request IDs, will be in the URL in the form
1164 1194
             // request_ids[0]=1001316103543&request_ids[1]=10100303657380180
1165  
-            NSMutableArray *requestIDs = [[NSMutableArray alloc] init];
  1195
+            NSMutableArray *requestIDs = [[[NSMutableArray alloc] init] autorelease];
1166 1196
             for (NSString *paramKey in params) {
1167 1197
                 if ([paramKey hasPrefix:@"request_ids"]) {
1168 1198
                     [requestIDs addObject:[params objectForKey:paramKey]];
10  sample/Hackbook/Hackbook/DataSet.m
@@ -101,13 +101,20 @@ - (id)init {
101 101
                                       @"Send request", @"button",
102 102
                                       @"getUserFriendTargetDialogRequest", @"method",
103 103
                                       nil];
  104
+        NSDictionary *requestMenu5 = [[NSDictionary alloc] initWithObjectsAndKeys:
  105
+                                      @"Enable frictionless requests", @"title",
  106
+                                      @"To enable a no-prompt request and invite experience, enable frictionless requests.", @"description",
  107
+                                      @"Enable frictionless", @"button",
  108
+                                      @"enableFrictionlessAppRequests", @"method",
  109
+                                      nil];
104 110
 
105 111
         NSArray *requestMenuItems = [[NSArray alloc] initWithObjects:
106 112
                                      requestMenu1,
107 113
                                      requestMenu2,
108 114
                                      requestMenu3,
109 115
                                      requestMenu4,
110  
-                                  nil];
  116
+                                     requestMenu5,
  117
+                                     nil];
111 118
 
112 119
         NSDictionary *requestConfigData = [[NSDictionary alloc] initWithObjectsAndKeys:
113 120
                                         @"Requests", @"title",
@@ -122,6 +129,7 @@ - (id)init {
122 129
         [requestMenu2 release];
123 130
         [requestMenu3 release];
124 131
         [requestMenu4 release];
  132
+        [requestMenu5 release];
125 133
         [requestMenuItems release];
126 134
         [requestConfigData release];
127 135
 
14  sample/Hackbook/Hackbook/HackbookAppDelegate.m
@@ -49,28 +49,28 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
49 49
     self.navigationController = navController;
50 50
     [rootViewController release];
51 51
     [navController release];
52  
-
  52
+    
53 53
     // Initialize Facebook
54 54
     facebook = [[Facebook alloc] initWithAppId:kAppId andDelegate:rootViewController];
55  
-
  55
+    
56 56
     // Check and retrieve authorization information
57 57
     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
58 58
     if ([defaults objectForKey:@"FBAccessTokenKey"] && [defaults objectForKey:@"FBExpirationDateKey"]) {
59 59
         facebook.accessToken = [defaults objectForKey:@"FBAccessTokenKey"];
60 60
         facebook.expirationDate = [defaults objectForKey:@"FBExpirationDateKey"];
61 61
     }
62  
-
  62
+    
63 63
     // Initialize API data (for views, etc.)
64 64
     apiData = [[DataSet alloc] init];
65  
-
  65
+    
66 66
     // Initialize user permissions
67 67
     userPermissions = [[NSMutableDictionary alloc] initWithCapacity:1];
68  
-
  68
+    
69 69
     // Override point for customization after application launch.
70 70
     // Add the navigation controller's view to the window and display.
71 71
     self.window.rootViewController = self.navigationController;
72 72
     [self.window makeKeyAndVisible];
73  
-
  73
+    
74 74
     // Check App ID:
75 75
     // This is really a warning for the developer, this should not
76 76
     // happen in a completed app
@@ -119,7 +119,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
119 119
             [alertView release];
120 120
         }
121 121
     }
122  
-
  122
+    
123 123
     return YES;
124 124
 }
125 125
 
70  sample/Hackbook/Hackbook/RootViewController.m
@@ -44,7 +44,7 @@ - (void)dealloc {
44 44
 - (void)didReceiveMemoryWarning {
45 45
     // Releases the view if it doesn't have a superview.
46 46
     [super didReceiveMemoryWarning];
47  
-
  47
+    
48 48
     // Release any cached data, images, etc that aren't in use.
49 49
 }
50 50
 
@@ -61,9 +61,9 @@ - (void)apiFQLIMe {
61 61
                                    nil];
62 62
     HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
63 63
     [[delegate facebook] requestWithMethodName:@"fql.query"
64  
-                          andParams:params
65  
-                      andHttpMethod:@"POST"
66  
-                        andDelegate:self];
  64
+                                     andParams:params
  65
+                                 andHttpMethod:@"POST"
  66
+                                   andDelegate:self];
67 67
 }
68 68
 
69 69
 - (void)apiGraphUserPermissions {
@@ -80,11 +80,11 @@ - (void)apiGraphUserPermissions {
80 80
 
81 81
 - (void)showLoggedIn {
82 82
     [self.navigationController setNavigationBarHidden:NO animated:NO];
83  
-
  83
+    
84 84
     self.backgroundImageView.hidden = YES;
85 85
     loginButton.hidden = YES;
86 86
     self.menuTableView.hidden = NO;
87  
-
  87
+    
88 88
     [self apiFQLIMe];
89 89
 }
90 90
 
@@ -94,16 +94,16 @@ - (void)showLoggedIn {
94 94
 
95 95
 - (void)showLoggedOut {
96 96
     [self.navigationController setNavigationBarHidden:YES animated:NO];
97  
-
  97
+    
98 98
     self.menuTableView.hidden = YES;
99 99
     self.backgroundImageView.hidden = NO;
100 100
     loginButton.hidden = NO;
101  
-
  101
+    
102 102
     // Clear personal info
103 103
     nameLabel.text = @"";
104 104
     // Get the profile image
105 105
     [profilePhotoImageView setImage:nil];
106  
-
  106
+    
107 107
     [[self navigationController] popToRootViewControllerAnimated:YES];
108 108
 }
109 109
 
@@ -137,7 +137,7 @@ - (void)menuButtonClicked:(id)sender {
137 137
     // From this object we can then read the tag property to determine
138 138
     // which menu button was clicked.
139 139
     APICallsViewController *controller = [[APICallsViewController alloc]
140  
-                                       initWithIndex:[sender tag]];
  140
+                                          initWithIndex:[sender tag]];
141 141
     pendingApiCallsController = controller;
142 142
     [self.navigationController pushViewController:controller animated:YES];
143 143
     [controller release];
@@ -151,10 +151,10 @@ - (void)loadView {
151 151
     [view setBackgroundColor:[UIColor whiteColor]];
152 152
     self.view = view;
153 153
     [view release];
154  
-
  154
+    
155 155
     // Initialize permissions
156 156
     permissions = [[NSArray alloc] initWithObjects:@"offline_access", nil];
157  
-
  157
+    
158 158
     // Main menu items
159 159
     mainMenuItems = [[NSMutableArray alloc] initWithCapacity:1];
160 160
     HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
@@ -162,27 +162,27 @@ - (void)loadView {
162 162
     for (NSUInteger i=0; i < [apiInfo count]; i++) {
163 163
         [mainMenuItems addObject:[[apiInfo objectAtIndex:i] objectForKey:@"title"]];
164 164
     }
165  
-
  165
+    
166 166
     // Set up the view programmatically
167 167
     self.view.backgroundColor = [UIColor whiteColor];
168  
-
  168
+    
169 169
     self.navigationItem.title = @"Hackbook for iOS";
170  
-
  170
+    
171 171
     self.navigationItem.backBarButtonItem =
172 172
     [[[UIBarButtonItem alloc] initWithTitle:@"Back"
173 173
                                       style:UIBarButtonItemStyleBordered
174 174
                                      target:nil
175 175
                                      action:nil] autorelease];
176  
-
  176
+    
177 177
     // Background Image
178 178
     backgroundImageView = [[UIImageView alloc]
179  
-                          initWithFrame:CGRectMake(0,0,
180  
-                                                   self.view.bounds.size.width,
181  
-                                                   self.view.bounds.size.height)];
  179
+                           initWithFrame:CGRectMake(0,0,
  180
+                                                    self.view.bounds.size.width,
  181
+                                                    self.view.bounds.size.height)];
182 182
     [backgroundImageView setImage:[UIImage imageNamed:@"Default.png"]];
183 183
     //[backgroundImageView setAlpha:0.25];
184 184
     [self.view addSubview:backgroundImageView];
185  
-
  185
+    
186 186
     // Login Button
187 187
     loginButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
188 188
     CGFloat xLoginButtonOffset = self.view.center.x - (318/2);
@@ -199,7 +199,7 @@ - (void)loadView {
199 199
                  forState:UIControlStateHighlighted];
200 200
     [loginButton sizeToFit];
201 201
     [self.view addSubview:loginButton];
202  
-
  202
+    
203 203
     // Main Menu Table
204 204
     menuTableView = [[UITableView alloc] initWithFrame:self.view.bounds
205 205
                                                  style:UITableViewStylePlain];
@@ -210,7 +210,7 @@ - (void)loadView {
210 210
     menuTableView.delegate = self;
211 211
     menuTableView.hidden = YES;
212 212
     //[self.view addSubview:menuTableView];
213  
-
  213
+    
214 214
     // Table header
215 215
     headerView = [[UIView alloc]
216 216
                   initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 100)];
@@ -228,9 +228,9 @@ - (void)loadView {
228 228
     nameLabel.text = @"";
229 229
     [headerView addSubview:nameLabel];
230 230
     menuTableView.tableHeaderView = headerView;
231  
-
  231
+    
232 232
     [self.view addSubview:menuTableView];
233  
-
  233
+    
234 234
     pendingApiCallsController = nil;
235 235
 }
236 236
 
@@ -243,7 +243,7 @@ - (void)viewDidUnload {
243 243
 - (void)viewWillAppear:(BOOL)animated {
244 244
     //[self.navigationController setNavigationBarHidden:YES animated:animated];
245 245
     [super viewWillAppear:animated];
246  
-
  246
+    
247 247
     HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
248 248
     if (![[delegate facebook] isSessionValid]) {
249 249
         [self showLoggedOut];
@@ -279,13 +279,13 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger
279 279
 // Customize the appearance of table view cells.
280 280
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
281 281
     static NSString *CellIdentifier = @"Cell";
282  
-
  282
+    
283 283
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
284 284
     if (cell == nil) {
285 285
         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
286 286
         cell.selectionStyle = UITableViewCellSelectionStyleNone;
287 287
     }
288  
-
  288
+    
289 289
     //create the button
290 290
     UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
291 291
     button.frame = CGRectMake(20, 20, (cell.contentView.frame.size.width-40), 44);
@@ -299,12 +299,12 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N
299 299
     [button addTarget:self action:@selector(menuButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
300 300
     button.tag = indexPath.row;
301 301
     [cell.contentView addSubview:button];
302  
-
  302
+    
303 303
     return cell;
304 304
 }
305 305
 
306 306
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
307  
-
  307
+    
308 308
 }
309 309
 
310 310
 - (void)storeAuthData:(NSString *)accessToken expiresAt:(NSDate *)expiresAt {
@@ -320,10 +320,10 @@ - (void)storeAuthData:(NSString *)accessToken expiresAt:(NSDate *)expiresAt {
320 320
  */
321 321
 - (void)fbDidLogin {
322 322
     [self showLoggedIn];
323  
-
  323
+    
324 324
     HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
325 325
     [self storeAuthData:[[delegate facebook] accessToken] expiresAt:[[delegate facebook] expirationDate]];
326  
-
  326
+        
327 327
     [pendingApiCallsController userDidGrantPermission];
328 328
 }
329 329
 
@@ -344,14 +344,14 @@ -(void)fbDidNotLogin:(BOOL)cancelled {
344 344
  */
345 345
 - (void)fbDidLogout {
346 346
     pendingApiCallsController = nil;
347  
-
  347
+    
348 348
     // Remove saved authorization information if it exists and it is
349 349
     // ok to clear it (logout, session invalid, app unauthorized)
350 350
     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
351 351
     [defaults removeObjectForKey:@"FBAccessTokenKey"];
352 352
     [defaults removeObjectForKey:@"FBExpirationDateKey"];
353 353
     [defaults synchronize];
354  
-
  354
+    
355 355
     [self showLoggedOut];
356 356
 }
357 357
 
@@ -403,7 +403,7 @@ - (void)request:(FBRequest *)request didLoad:(id)result {
403 403
         nameLabel.text = [result objectForKey:@"name"];
404 404
         // Get the profile image
405 405
         UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[result objectForKey:@"pic"]]]];
406  
-
  406
+        
407 407
         // Resize, crop the image to make sure it is square and renders
408 408
         // well on Retina display
409 409
         float ratio;
@@ -428,7 +428,7 @@ - (void)request:(FBRequest *)request didLoad:(id)result {
428 428
         [image drawInRect:clipRect];
429 429
         UIImage *imgThumb =   UIGraphicsGetImageFromCurrentImageContext();
430 430
         [imgThumb retain];
431  
-
  431
+        
432 432
         [profilePhotoImageView setImage:imgThumb];
433 433
         [self apiGraphUserPermissions];
434 434
     } else {
33  src/FBDialog.h
@@ -6,18 +6,19 @@
6 6
  * You may obtain a copy of the License at
7 7
  *
8 8
  *    http://www.apache.org/licenses/LICENSE-2.0
9  
-
  9
+ 
10 10
  * Unless required by applicable law or agreed to in writing, software
11 11
  * distributed under the License is distributed on an "AS IS" BASIS,
12 12
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 13
  * See the License for the specific language governing permissions and
14 14
  * limitations under the License.
15  
-*/
  15
+ */
16 16
 
17 17
 #import <Foundation/Foundation.h>
18 18
 #import <UIKit/UIKit.h>
19 19
 
20 20
 @protocol FBDialogDelegate;
  21
+@class FBFrictionlessRequestSettings;
21 22
 
22 23
 /**
23 24
  * Do not use this interface directly, instead, use dialog in Facebook.h
@@ -26,18 +27,20 @@
26 27
  */
27 28
 
28 29
 @interface FBDialog : UIView <UIWebViewDelegate> {
29  
-  id<FBDialogDelegate> _delegate;
30  
-  NSMutableDictionary *_params;
31  
-  NSString * _serverURL;
32  
-  NSURL* _loadingURL;
33  
-  UIWebView* _webView;
34  
-  UIActivityIndicatorView* _spinner;
35  
-  UIButton* _closeButton;
36  
-  UIInterfaceOrientation _orientation;
37  
-  BOOL _showingKeyboard;
38  
-
39  
-  // Ensures that UI elements behind the dialog are disabled.
40  
-  UIView* _modalBackgroundView;
  30
+    id<FBDialogDelegate> _delegate;
  31
+    NSMutableDictionary *_params;
  32
+    NSString * _serverURL;
  33
+    NSURL* _loadingURL;
  34
+    UIWebView* _webView;
  35
+    UIActivityIndicatorView* _spinner;
  36
+    UIButton* _closeButton;
  37
+    UIInterfaceOrientation _orientation;
  38
+    BOOL _showingKeyboard;
  39
+    BOOL _isViewInvisible;
  40
+    FBFrictionlessRequestSettings* _frictionlessSettings;
  41
+    
  42
+    // Ensures that UI elements behind the dialog are disabled.
  43
+    UIView* _modalBackgroundView;
41 44
 }
42 45
 
43 46
 /**
@@ -54,6 +57,8 @@
54 57
 
55 58
 - (id)initWithURL: (NSString *) loadingURL
56 59
            params: (NSMutableDictionary *) params
  60
+  isViewInvisible: (BOOL) isViewInvisible
  61
+    frictionlessSettings: (FBFrictionlessRequestSettings *) frictionlessSettings
57 62
          delegate: (id <FBDialogDelegate>) delegate;
58 63
 
59 64
 /**
154  src/FBDialog.m
@@ -17,6 +17,8 @@
17 17
 
18 18
 #import "FBDialog.h"
19 19
 #import "Facebook.h"
  20
+#import "FBFrictionlessRequestSettings.h"
  21
+#import "JSON.h"
20 22
 
21 23
 ///////////////////////////////////////////////////////////////////////////////////////////////////
22 24
 // global
@@ -263,7 +265,13 @@ - (void)postDismissCleanup {
263 265
 
264 266
 - (void)dismiss:(BOOL)animated {
265 267
     [self dialogWillDisappear];
266  
-    
  268
+
  269
+    // If the dialog has been closed, then we need to cancel the order to open it.	
  270
+    // This happens in the case of a frictionless request, see webViewDidFinishLoad for details	
  271
+    [NSObject cancelPreviousPerformRequestsWithTarget:self 
  272
+                                             selector:@selector(showWebView)
  273
+                                               object:nil];
  274
+
267 275
     [_loadingURL release];
268 276
     _loadingURL = nil;
269 277
     
@@ -283,6 +291,39 @@ - (void)cancel {
283 291
     [self dialogDidCancel:nil];
284 292
 }
285 293
 
  294
+- (BOOL)testBoolUrlParam:(NSURL *)url param:(NSString *)param {
  295
+    NSString *paramVal = [self getStringFromUrl: [url absoluteString] 
  296
+                                         needle: param];
  297
+    return [paramVal boolValue];
  298
+}
  299
+
  300
+- (void)dialogSuccessHandleFrictionlessResponses:(NSURL *)url {
  301
+    // did we receive a recipient list?
  302
+    NSString *recipientJson = [self getStringFromUrl:[url absoluteString]
  303
+                                              needle:@"frictionless_recipients="];
  304
+    if (recipientJson) {
  305
+        // if value parses as an array, treat as set of fbids
  306
+        SBJsonParser *parser = [[[SBJsonParser alloc]
  307
+                                 init]
  308
+                                autorelease];
  309
+        id recipients = [parser objectWithString:recipientJson];
  310
+
  311
+        // if we got something usable, copy the ids out and update the cache
  312
+        if ([recipients isKindOfClass:[NSArray class]]) { 
  313
+            NSMutableArray *ids = [[[NSMutableArray alloc]
  314
+                                    initWithCapacity:[recipients count]]
  315
+                                   autorelease];
  316
+            for (id recipient in recipients) {
  317
+                NSString *fbid = [NSString stringWithFormat:@"%@", recipient];
  318
+                [ids addObject:fbid];
  319
+            }
  320
+            // we may be tempted to terminate outstanding requests before this
  321
+            // point, but that would cause problems if the user cancelled a dialog
  322
+            [_frictionlessSettings updateRecipientCacheWithRecipients:ids];
  323
+        }
  324
+    }
  325
+}
  326
+
286 327
 ///////////////////////////////////////////////////////////////////////////////////////////////////
287 328
 // NSObject
288 329
 
@@ -344,6 +385,7 @@ - (void)dealloc {
344 385
     [_closeButton release];
345 386
     [_loadingURL release];
346 387
     [_modalBackgroundView release];
  388
+    [_frictionlessSettings release];
347 389
     [super dealloc];
348 390
 }
349 391
 
@@ -360,6 +402,41 @@ - (void)drawRect:(CGRect)rect {
360 402
     [self strokeLines:webRect stroke:kBorderBlack];
361 403
 }
362 404
 
  405
+// Display the dialog's WebView with a slick pop-up animation	
  406
+- (void)showWebView {	
  407
+    UIWindow* window = [UIApplication sharedApplication].keyWindow;	
  408
+    if (!window) {	
  409
+        window = [[UIApplication sharedApplication].windows objectAtIndex:0];	
  410
+    }	
  411
+    _modalBackgroundView.frame = window.frame;	
  412
+    [_modalBackgroundView addSubview:self];	
  413
+    [window addSubview:_modalBackgroundView];	
  414
+    
  415
+    self.transform = CGAffineTransformScale([self transformForOrientation], 0.001, 0.001);	
  416
+    [UIView beginAnimations:nil context:nil];	
  417
+    [UIView setAnimationDuration:kTransitionDuration/1.5];	
  418
+    [UIView setAnimationDelegate:self];	
  419
+    [UIView setAnimationDidStopSelector:@selector(bounce1AnimationStopped)];	
  420
+    self.transform = CGAffineTransformScale([self transformForOrientation], 1.1, 1.1);	
  421
+    [UIView commitAnimations];	
  422
+    
  423
+    [self dialogWillAppear];	
  424
+    [self addObservers];	
  425
+}	
  426
+
  427
+// Show a spinner during the loading time for the dialog. This is designed to show	
  428
+// on top of the webview but before the contents have loaded.	
  429
+- (void)showSpinner {	
  430
+    [_spinner sizeToFit];	
  431
+    [_spinner startAnimating];	
  432
+    _spinner.center = _webView.center;	
  433
+}	
  434
+
  435
+- (void)hideSpinner {	
  436
+    [_spinner stopAnimating];	
  437
+    _spinner.hidden = YES;	
  438
+}	
  439
+
363 440
 ///////////////////////////////////////////////////////////////////////////////////////////////////
364 441
 // UIWebViewDelegate
365 442
 
@@ -381,6 +458,9 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)
381 458
                 [self dialogDidCancel:url];
382 459
             }
383 460
         } else {
  461
+            if (_frictionlessSettings.enabled) {
  462
+                [self dialogSuccessHandleFrictionlessResponses:url];
  463
+            }
384 464
             [self dialogDidSucceed:url];
385 465
         }
386 466
         return NO;
@@ -401,9 +481,15 @@ - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)
401 481
 }
402 482
 
403 483
 - (void)webViewDidFinishLoad:(UIWebView *)webView {
404  
-    [_spinner stopAnimating];
405  
-    _spinner.hidden = YES;
406  
-    
  484
+    if (_isViewInvisible) {
  485
+        // if our cache asks us to hide the view, then we do, but
  486
+        // in case of a stale cache, we will display the view in a moment
  487
+        // note that showing the view now would cause a visible white
  488
+        // flash in the common case where the cache is up to date
  489
+        [self performSelector:@selector(showWebView) withObject:nil afterDelay:.05]; 	
  490
+    } else {
  491
+        [self hideSpinner];	
  492
+    }
407 493
     [self updateWebOrientation];
408 494
 }
409 495
 
@@ -475,25 +561,35 @@ - (NSString *) getStringFromUrl: (NSString*) url needle:(NSString *) needle {
475 561
     NSString * str = nil;
476 562
     NSRange start = [url rangeOfString:needle];
477 563
     if (start.location != NSNotFound) {
478  
-        NSRange end = [[url substringFromIndex:start.location+start.length] rangeOfString:@"&"];
479  
-        NSUInteger offset = start.location+start.length;
480  
-        str = end.location == NSNotFound
481  
-        ? [url substringFromIndex:offset]
482  
-        : [url substringWithRange:NSMakeRange(offset, end.location)];
483  
-        str = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
484  
-    }
485  
-    
  564
+        // confirm that the parameter is not a partial name match
  565
+        unichar c = '?';
  566
+        if (start.location != 0) {
  567
+            c = [url characterAtIndex:start.location - 1];
  568
+        }
  569
+        if (c == '?' || c == '&') {        
  570
+            NSRange end = [[url substringFromIndex:start.location+start.length] rangeOfString:@"&"];
  571
+            NSUInteger offset = start.location+start.length;
  572
+            str = end.location == NSNotFound ?
  573
+                [url substringFromIndex:offset] : 
  574
+                [url substringWithRange:NSMakeRange(offset, end.location)];
  575
+            str = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  576
+        }
  577
+    }    
486 578
     return str;
487 579
 }
488 580
 
489 581
 - (id)initWithURL: (NSString *) serverURL
490 582
            params: (NSMutableDictionary *) params
  583
+  isViewInvisible: (BOOL)isViewInvisible
  584
+     frictionlessSettings: (FBFrictionlessRequestSettings*) frictionlessSettings
491 585
          delegate: (id <FBDialogDelegate>) delegate {
492 586
     
493 587
     self = [self init];
494 588
     _serverURL = [serverURL retain];
495  
-    _params = [params retain];
  589
+    _params = [params retain];    
496 590
     _delegate = delegate;
  591
+    _isViewInvisible = isViewInvisible;
  592
+    _frictionlessSettings = [frictionlessSettings retain];
497 593
     
498 594
     return self;
499 595
 }
@@ -503,7 +599,7 @@ - (void)load {
503 599
 }
504 600
 
505 601
 - (void)loadURL:(NSString*)url get:(NSDictionary*)getParams {
506  
-    
  602
+
507 603
     [_loadingURL release];
508 604
     _loadingURL = [[self generateURL:url params:getParams] retain];
509 605
     NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:_loadingURL];
@@ -530,32 +626,10 @@ - (void)show {
530 626
                                 innerWidth,
531 627
                                 self.frame.size.height - (1 + kBorderWidth*2));
532 628
     
533  
-    [_spinner sizeToFit];
534  
-    [_spinner startAnimating];
535  
-    _spinner.center = _webView.center;
536  
-    
537  
-    UIWindow* window = [UIApplication sharedApplication].keyWindow;
538  
-    if (!window) {
539  
-        window = [[UIApplication sharedApplication].windows objectAtIndex:0];
  629
+    if (!_isViewInvisible) {	
  630
+        [self showSpinner];	
  631
+        [self showWebView];
540 632
     }
541  
-    
542  
-    _modalBackgroundView.frame = window.frame;
543  
-    [_modalBackgroundView addSubview:self];
544  
-    [window addSubview:_modalBackgroundView];
545  
-    
546  
-    [window addSubview:self];
547  
-    
548  
-    [self dialogWillAppear];
549  
-    
550  
-    self.transform = CGAffineTransformScale([self transformForOrientation], 0.001, 0.001);
551  
-    [UIView beginAnimations:nil context:nil];
552  
-    [UIView setAnimationDuration:kTransitionDuration/1.5];
553  
-    [UIView setAnimationDelegate:self];
554  
-    [UIView setAnimationDidStopSelector:@selector(bounce1AnimationStopped)];
555  
-    self.transform = CGAffineTransformScale([self transformForOrientation], 1.1, 1.1);
556  
-    [UIView commitAnimations];
557  
-    
558  
-    [self addObservers];
559 633
 }
560 634
 
561 635
 - (void)dismissWithSuccess:(BOOL)success animated:(BOOL)animated {
@@ -601,4 +675,4 @@ - (void)dialogDidCancel:(NSURL *)url {
601 675
     [self dismissWithSuccess:NO animated:YES];
602 676
 }
603 677
 
604  
-@end
  678
+@end
74  src/FBFrictionlessRequestSettings.h
... ...
@@ -0,0 +1,74 @@
  1
+/*
  2
+ * Copyright 2012 Facebook
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *    http://www.apache.org/licenses/LICENSE-2.0
  9
+ 
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+
  17
+#import <Foundation/Foundation.h>
  18
+
  19
+@class Facebook;
  20
+
  21
+/**
  22
+ * Do not use this interface directly, instead, use methods in Facebook.h
  23
+ *
  24
+ * Handles frictionless interaction and recipient-caching by the SDK,
  25
+ * see https://developers.facebook.com/docs/reference/dialogs/requests/ 
  26
+ */
  27
+@interface FBFrictionlessRequestSettings : NSObject<FBRequestDelegate> {
  28
+@private
  29
+    NSArray*            _allowedRecipients;
  30
+    FBRequest*          _activeRequest;
  31
+    BOOL                _enabled;
  32
+}
  33
+
  34
+/**
  35
+ * BOOL indicating whether frictionless request sending has been enabled
  36
+ */
  37
+@property(nonatomic, readonly) BOOL enabled;
  38
+
  39
+/**
  40
+ * Enable frictionless request sending by the sdk; this means:
  41
+ *   1. query and cache the current set of frictionless recipients
  42
+ *   2. flag other facets of the sdk to behave in a frictionless way
  43
+ */
  44
+- (void)enableWithFacebook:(Facebook*)facebook;
  45
+
  46
+/**
  47
+ * Reload recipient cache; called by the sdk to keep the cache fresh;
  48
+ * method makes graph request: me/apprequestformerrecipients
  49
+ */
  50
+- (void)reloadRecipientCacheWithFacebook:(Facebook*)facebook;
  51
+
  52
+/**
  53
+ * Update the recipient cache; called by the sdk to keep the cache fresh;
  54
+ */
  55
+- (void)updateRecipientCacheWithRecipients:(NSArray*)ids;
  56
+
  57
+/**
  58
+ * Given an fbID for a user, indicates whether user is enabled for
  59
+ * frictionless calls
  60
+ */
  61
+- (BOOL)isFrictionlessEnabledForRecipient:(id)fbid;
  62
+
  63
+/**
  64
+ * Given an array of user fbIDs, indicates whether they are enabled for
  65
+ * frictionless calls
  66
+ */
  67
+- (BOOL)isFrictionlessEnabledForRecipients:(NSArray*)fbids;
  68
+
  69
+/**
  70
+ * init the frictionless cache object
  71
+ */
  72
+- (id)init;
  73
+
  74
+@end
162  src/FBFrictionlessRequestSettings.m
... ...
@@ -0,0 +1,162 @@
  1
+/*
  2
+ * Copyright 2012 Facebook
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License");
  5
+ * you may not use this file except in compliance with the License.
  6
+ * You may obtain a copy of the License at
  7
+ *
  8
+ *    http://www.apache.org/licenses/LICENSE-2.0
  9
+ 
  10
+ * Unless required by applicable law or agreed to in writing, software
  11
+ * distributed under the License is distributed on an "AS IS" BASIS,
  12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13
+ * See the License for the specific language governing permissions and
  14
+ * limitations under the License.
  15
+ */
  16
+
  17
+#import "Facebook.h"
  18
+#import "FBFrictionlessRequestSettings.h"
  19
+
  20
+///////////////////////////////////////////////////////////////////////////////////////////////////
  21
+// private interface
  22
+//
  23
+@interface FBFrictionlessRequestSettings ()
  24
+
  25
+@property (readwrite, retain) NSArray *     allowedRecipients;
  26
+@property (readwrite, retain) FBRequest*    activeRequest;
  27
+
  28
+@end
  29
+
  30
+///////////////////////////////////////////////////////////////////////////////////////////////////
  31
+
  32
+@implementation FBFrictionlessRequestSettings 
  33
+
  34
+///////////////////////////////////////////////////////////////////////////////////////////////////
  35
+// public
  36
+
  37
+@synthesize enabled = _enabled;
  38
+
  39
+- (id)init {
  40
+    if (self = [super init]) {
  41
+        // start life with an empty frictionless cache
  42
+        self.allowedRecipients = [[[NSArray alloc] init] autorelease];
  43
+    }
  44
+    return self;
  45
+}
  46
+
  47
+- (void)enableWithFacebook:(Facebook*)facebook {
  48
+    if (!_enabled) {
  49
+        _enabled = true;
  50
+        [self reloadRecipientCacheWithFacebook:facebook];
  51
+    }
  52
+}
  53
+
  54
+- (void)reloadRecipientCacheWithFacebook:(Facebook *)facebook {
  55
+    // request the list of frictionless recipients from the server
  56
+    id request = [facebook requestWithGraphPath:@"me/apprequestformerrecipients"
  57
+                                    andDelegate:self];
  58
+    if (request) {
  59
+        self.activeRequest = request;
  60
+    }    
  61
+}
  62
+
  63
+- (void)updateRecipientCacheWithRecipients:(NSArray*)ids {
  64
+    // if setting recipients directly, no need to complete pending request
  65
+    self.activeRequest = nil;
  66
+    
  67
+    if (ids == nil) {
  68
+        self.allowedRecipients = [[[NSArray alloc] init] autorelease];
  69
+    } else {
  70
+        self.allowedRecipients = [[[NSArray alloc] initWithArray:ids] autorelease];
  71
+    }
  72
+}
  73
+
  74
+- (BOOL)isFrictionlessEnabledForRecipient:(NSString *)fbid {
  75
+    // trim whitespace from edges
  76
+    fbid = [fbid stringByTrimmingCharactersInSet:
  77
+                             [NSCharacterSet whitespaceCharacterSet]];
  78
+
  79
+    // linear search through cache for a match
  80
+    for (NSString *entry in self.allowedRecipients) {
  81
+        if ([entry isEqualToString:fbid]) {
  82
+            return YES;
  83
+        }
  84
+    }
  85
+    return NO;
  86
+}
  87
+
  88
+- (BOOL)isFrictionlessEnabledForRecipients:(NSArray*)fbids {
  89
+    // we handle arrays of NSString and NSNumber, and throw on anything else
  90
+    for (id fbid in fbids) {
  91
+        NSString* fbidstr;
  92
+        // give us a number, and we convert it to a string
  93
+        if ([fbid isKindOfClass:[NSNumber class]]) {
  94
+            fbidstr = [(NSNumber*)fbid stringValue];
  95
+        } else if ([fbid isKindOfClass:[NSString class]]) {
  96
+            // or give us a string, and we just use it as is
  97
+            fbidstr = (NSString*)fbid;
  98
+        } else {
  99
+            // unexpected type found in the array of fbids
  100
+            @throw [NSException exceptionWithName:NSInvalidArgumentException
  101
+                                           reason:@"items in fbids must be NSString or NSNumber"
  102
+                                         userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
  103
+                                                   [fbid class], @"invalid class", 
  104
+                                                   nil]];
  105
+        }
  106
+        
  107
+        // if we miss our cache once, we fail the set
  108
+        if (![self isFrictionlessEnabledForRecipient:fbidstr]) {
  109
+            return NO;
  110
+        }
  111
+    }
  112
+    return YES;
  113
+}
  114
+
  115
+///////////////////////////////////////////////////////////////////////////////////////////////////
  116
+// FBRequestDelegate
  117
+
  118
+- (void)request:(FBRequest *)request
  119
+        didLoad:(id)result {
  120
+
  121
+    // a little request bookkeeping
  122
+    self.activeRequest = nil;
  123
+
  124
+    int items = [[result objectForKey: @"data"] count];
  125
+    NSMutableArray* recipients = [[[NSMutableArray alloc] initWithCapacity: items] autorelease];
  126
+        
  127
+    for (int i = 0; i < items; i++) {
  128
+        [recipients addObject: [[[result objectForKey: @"data"] 
  129
+                                 objectAtIndex: i] 
  130
+                                objectForKey: @"recipient_id"]] ;
  131
+    }
  132
+        
  133
+    self.allowedRecipients = recipients;        
  134
+}
  135
+
  136
+- (void)request:(FBRequest *)request didFailWithError:(NSError *)error {
  137
+    // if the request to load the frictionless recipients fails, proceed without updating 
  138
+    // the recipients cache; the cache may become invalid due to a failed update or other reasons
  139
+    // (e.g. simultaneous use of the same app from multiple devices), in the case of an invalid
  140
+    // cache, a request dialog may either appear a moment later than it usually would, or appear
  141
+    // briefly when it should not appear at all; in either case the correct request behavior 
  142
+    // occurs, and the cache is updated
  143
+    self.activeRequest = nil;
  144
+}
  145
+
  146
+///////////////////////////////////////////////////////////////////////////////////////////////////
  147
+// NSObject
  148
+
  149
+- (void)dealloc {    
  150
+    self.activeRequest = nil;
  151
+    self.allowedRecipients = nil;
  152
+    [super dealloc];
  153
+}
  154
+
  155
+///////////////////////////////////////////////////////////////////////////////////////////////////
  156
+// private helpers
  157
+// 
  158
+
  159
+@synthesize allowedRecipients = _allowedRecipients;
  160
+@synthesize activeRequest = _activeRequest;
  161
+
  162
+@end
6  src/FBLoginDialog.h
@@ -6,7 +6,7 @@
6 6
  * You may obtain a copy of the License at
7 7
  *
8 8
  *    http://www.apache.org/licenses/LICENSE-2.0
9  
-
  9
+ 
10 10
  * Unless required by applicable law or agreed to in writing, software
11 11
  * distributed under the License is distributed on an "AS IS" BASIS,
12 12
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,12 +27,12 @@
27 27
  */
28 28
 
29 29
 @interface FBLoginDialog : FBDialog {
30  
-  id<FBLoginDialogDelegate> _loginDelegate;
  30
+    id<FBLoginDialogDelegate> _loginDelegate;
31 31
 }
32 32
 
33 33
 -(id) initWithURL:(NSString *) loginURL
34 34
       loginParams:(NSMutableDictionary *) params
35  
-      delegate:(id <FBLoginDialogDelegate>) delegate;
  35
+         delegate:(id <FBLoginDialogDelegate>) delegate;
36 36
 @end
37 37
 
38 38
 ///////////////////////////////////////////////////////////////////////////////////////////////////
76  src/FBLoginDialog.m
@@ -30,12 +30,12 @@ @implementation FBLoginDialog
30 30
 - (id)initWithURL:(NSString*) loginURL 
31 31
       loginParams:(NSMutableDictionary*) params 
32 32
          delegate:(id <FBLoginDialogDelegate>) delegate{
33  
-  
34  
-  self = [super init];
35  
-  _serverURL = [loginURL retain];
36  
-  _params = [params retain];
37  
-  _loginDelegate = delegate;
38  
-  return self;
  33
+    
  34
+    self = [super init];
  35
+    _serverURL = [loginURL retain];
  36
+    _params = [params retain];
  37
+    _loginDelegate = delegate;
  38
+    return self;
39 39
 }
40 40
 
41 41
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -45,50 +45,50 @@ - (id)initWithURL:(NSString*) loginURL
45 45
  * Override FBDialog : to call when the webView Dialog did succeed
46 46
  */
47 47
 - (void) dialogDidSucceed:(NSURL*)url {
48  
-  NSString *q = [url absoluteString];
49  
-  NSString *token = [self getStringFromUrl:q needle:@"access_token="];
50  
-  NSString *expTime = [self getStringFromUrl:q needle:@"expires_in="];
51  
-  NSDate *expirationDate =nil;
52  
-  
53  
-  if (expTime != nil) {
54  
-    int expVal = [expTime intValue];
55  
-    if (expVal == 0) {
56  
-      expirationDate = [NSDate distantFuture];
57  
-    } else {
58  
-      expirationDate = [NSDate dateWithTimeIntervalSinceNow:expVal];
  48
+    NSString *q = [url absoluteString];
  49
+    NSString *token = [self getStringFromUrl:q needle:@"access_token="];
  50
+    NSString *expTime = [self getStringFromUrl:q needle:@"expires_in="];
  51
+    NSDate *expirationDate =nil;
  52
+    
  53
+    if (expTime != nil) {
  54
+        int expVal = [expTime intValue];
  55
+        if (expVal == 0) {
  56
+            expirationDate = [NSDate distantFuture];
  57
+        } else {
  58
+            expirationDate = [NSDate dateWithTimeIntervalSinceNow:expVal];
  59
+        } 
59 60
     } 
60  
-  } 
61  
-  
62  
-  if ((token == (NSString *) [NSNull null]) || (token.length == 0)) {
63  
-    [self dialogDidCancel:url];
64  
-    [self dismissWithSuccess:NO animated:YES];
65  
-  } else {
66  
-    if ([_loginDelegate respondsToSelector:@selector(fbDialogLogin:expirationDate:)]) {
67  
-      [_loginDelegate fbDialogLogin:token expirationDate:expirationDate];
  61
+    
  62
+    if ((token == (NSString *) [NSNull null]) || (token.length == 0)) {
  63
+        [self dialogDidCancel:url];
  64
+        [self dismissWithSuccess:NO animated:YES];
  65
+    } else {
  66
+        if ([_loginDelegate respondsToSelector:@selector(fbDialogLogin:expirationDate:)]) {
  67
+            [_loginDelegate fbDialogLogin:token expirationDate:expirationDate];
  68
+        }
  69
+        [self dismissWithSuccess:YES animated:YES];
68 70
     }
69  
-    [self dismissWithSuccess:YES animated:YES];
70  
-  }
71  
-  
  71
+    
72 72
 }
73 73
 
74 74
 /**
75 75
  * Override FBDialog : to call with the login dialog get canceled 
76 76
  */
77 77
 - (void)dialogDidCancel:(NSURL *)url {
78  
-  [self dismissWithSuccess:NO animated:YES];
79  
-  if ([_loginDelegate respondsToSelector:@selector(fbDialogNotLogin:)]) {
80  
-    [_loginDelegate fbDialogNotLogin:YES];
81  
-  }
  78
+    [self dismissWithSuccess:NO animated:YES];
  79
+    if ([_loginDelegate respondsToSelector:@selector(fbDialogNotLogin:)]) {
  80
+        [_loginDelegate fbDialogNotLogin:YES];
  81
+    }
82 82
 }
83 83
 
84 84
 - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
85  
-  if (!(([error.domain isEqualToString:@"NSURLErrorDomain"] && error.code == -999) ||