Skip to content
Browse files

first commit

  • Loading branch information...
0 parents commit dae3504aae4bd857c9e42cb62784dce2c4dd74ed @brow committed
Showing with 5,605 additions and 0 deletions.
  1. +1 −0 .gitattributes
  2. +9 −0 .gitignore
  3. +22 −0 FBConnect/FBConnect.h
  4. BIN FBConnect/FBDialog.bundle/images/close.png
  5. BIN FBConnect/FBDialog.bundle/images/fbicon.png
  6. +167 −0 FBConnect/FBDialog.h
  7. +656 −0 FBConnect/FBDialog.m
  8. +48 −0 FBConnect/FBLoginDialog.h
  9. +94 −0 FBConnect/FBLoginDialog.m
  10. +116 −0 FBConnect/FBRequest.h
  11. +363 −0 FBConnect/FBRequest.m
  12. +113 −0 FBConnect/Facebook.h
  13. +660 −0 FBConnect/Facebook.m
  14. +50 −0 FBConnect/JSON/JSON.h
  15. +68 −0 FBConnect/JSON/NSObject+SBJSON.h
  16. +53 −0 FBConnect/JSON/NSObject+SBJSON.m
  17. +58 −0 FBConnect/JSON/NSString+SBJSON.h
  18. +55 −0 FBConnect/JSON/NSString+SBJSON.m
  19. +75 −0 FBConnect/JSON/SBJSON.h
  20. +212 −0 FBConnect/JSON/SBJSON.m
  21. +86 −0 FBConnect/JSON/SBJsonBase.h
  22. +78 −0 FBConnect/JSON/SBJsonBase.m
  23. +87 −0 FBConnect/JSON/SBJsonParser.h
  24. +475 −0 FBConnect/JSON/SBJsonParser.m
  25. +129 −0 FBConnect/JSON/SBJsonWriter.h
  26. +237 −0 FBConnect/JSON/SBJsonWriter.m
  27. +73 −0 FacebookLikeView/Classes/FacebookLikeView.h
  28. +155 −0 FacebookLikeView/Classes/FacebookLikeView.m
  29. +38 −0 FacebookLikeView/Resources/FacebookLikeView.html
  30. +411 −0 FacebookLikeViewDemo.xcodeproj/project.pbxproj
  31. +7 −0 FacebookLikeViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  32. +16 −0 FacebookLikeViewDemo/Facebook+Extras.h
  33. +33 −0 FacebookLikeViewDemo/Facebook+Extras.m
  34. +38 −0 FacebookLikeViewDemo/FacebookLikeViewDemo-Info.plist
  35. +14 −0 FacebookLikeViewDemo/FacebookLikeViewDemo-Prefix.pch
  36. +21 −0 FacebookLikeViewDemo/FacebookLikeViewDemoAppDelegate.h
  37. +36 −0 FacebookLikeViewDemo/FacebookLikeViewDemoAppDelegate.m
  38. +18 −0 FacebookLikeViewDemo/FacebookLikeViewDemoViewController.h
  39. +80 −0 FacebookLikeViewDemo/FacebookLikeViewDemoViewController.m
  40. +49 −0 FacebookLikeViewDemo/LikeButtonDemo-Info.plist
  41. +14 −0 FacebookLikeViewDemo/LikeButtonDemo-Prefix.pch
  42. +227 −0 FacebookLikeViewDemo/en.lproj/FacebookLikeViewDemoViewController.xib
  43. +2 −0 FacebookLikeViewDemo/en.lproj/InfoPlist.strings
  44. +444 −0 FacebookLikeViewDemo/en.lproj/MainWindow.xib
  45. +17 −0 FacebookLikeViewDemo/main.m
1 .gitattributes
@@ -0,0 +1 @@
+*.pbxproj -crlf
9 .gitignore
@@ -0,0 +1,9 @@
+# xcode noise
+build
+*.pbxuser
+*.mode1v3
+*.mode2v3
+xcuserdata
+
+# osx noise
+.DS_Store
22 FBConnect/FBConnect.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010 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.
+ */
+
+
+#include "Facebook.h"
+#include "FBDialog.h"
+#include "FBLoginDialog.h"
+#include "FBRequest.h"
+#include "SBJSON.h"
BIN FBConnect/FBDialog.bundle/images/close.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN FBConnect/FBDialog.bundle/images/fbicon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
167 FBConnect/FBDialog.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2010 Facebook
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+@protocol FBDialogDelegate;
+
+/**
+ * Do not use this interface directly, instead, use dialog in Facebook.h
+ *
+ * Facebook dialog interface for start the facebook webView UIServer Dialog.
+ */
+
+@interface FBDialog : UIView <UIWebViewDelegate> {
+ id<FBDialogDelegate> _delegate;
+ NSMutableDictionary *_params;
+ NSString * _serverURL;
+ NSURL* _loadingURL;
+ UIWebView* _webView;
+ UIActivityIndicatorView* _spinner;
+ UIImageView* _iconView;
+ UILabel* _titleLabel;
+ UIButton* _closeButton;
+ UIDeviceOrientation _orientation;
+ BOOL _showingKeyboard;
+
+ // Ensures that UI elements behind the dialog are disabled.
+ UIView* _modalBackgroundView;
+}
+
+/**
+ * The delegate.
+ */
+@property(nonatomic,assign) id<FBDialogDelegate> delegate;
+
+/**
+ * The parameters.
+ */
+@property(nonatomic, retain) NSMutableDictionary* params;
+
+/**
+ * The title that is shown in the header atop the view.
+ */
+@property(nonatomic,copy) NSString* title;
+
+- (NSString *) getStringFromUrl: (NSString*) url needle:(NSString *) needle;
+
+- (id)initWithURL: (NSString *) loadingURL
+ params: (NSMutableDictionary *) params
+ delegate: (id <FBDialogDelegate>) delegate;
+
+/**
+ * Displays the view with an animation.
+ *
+ * The view will be added to the top of the current key window.
+ */
+- (void)show;
+
+/**
+ * Displays the first page of the dialog.
+ *
+ * Do not ever call this directly. It is intended to be overriden by subclasses.
+ */
+- (void)load;
+
+/**
+ * Displays a URL in the dialog.
+ */
+- (void)loadURL:(NSString*)url
+ get:(NSDictionary*)getParams;
+
+/**
+ * Hides the view and notifies delegates of success or cancellation.
+ */
+- (void)dismissWithSuccess:(BOOL)success animated:(BOOL)animated;
+
+/**
+ * Hides the view and notifies delegates of an error.
+ */
+- (void)dismissWithError:(NSError*)error animated:(BOOL)animated;
+
+/**
+ * Subclasses may override to perform actions just prior to showing the dialog.
+ */
+- (void)dialogWillAppear;
+
+/**
+ * Subclasses may override to perform actions just after the dialog is hidden.
+ */
+- (void)dialogWillDisappear;
+
+/**
+ * Subclasses should override to process data returned from the server in a 'fbconnect' url.
+ *
+ * Implementations must call dismissWithSuccess:YES at some point to hide the dialog.
+ */
+- (void)dialogDidSucceed:(NSURL *)url;
+
+/**
+ * Subclasses should override to process data returned from the server in a 'fbconnect' url.
+ *
+ * Implementations must call dismissWithSuccess:YES at some point to hide the dialog.
+ */
+- (void)dialogDidCancel:(NSURL *)url;
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*
+ *Your application should implement this delegate
+ */
+@protocol FBDialogDelegate <NSObject>
+
+@optional
+
+/**
+ * Called when the dialog succeeds and is about to be dismissed.
+ */
+- (void)dialogDidComplete:(FBDialog *)dialog;
+
+/**
+ * Called when the dialog succeeds with a returning url.
+ */
+- (void)dialogCompleteWithUrl:(NSURL *)url;
+
+/**
+ * Called when the dialog get canceled by the user.
+ */
+- (void)dialogDidNotCompleteWithUrl:(NSURL *)url;
+
+/**
+ * Called when the dialog is cancelled and is about to be dismissed.
+ */
+- (void)dialogDidNotComplete:(FBDialog *)dialog;
+
+/**
+ * Called when dialog failed to load due to an error.
+ */
+- (void)dialog:(FBDialog*)dialog didFailWithError:(NSError *)error;
+
+/**
+ * Asks if a link touched by a user should be opened in an external browser.
+ *
+ * If a user touches a link, the default behavior is to open the link in the Safari browser,
+ * which will cause your app to quit. You may want to prevent this from happening, open the link
+ * in your own internal browser, or perhaps warn the user that they are about to leave your app.
+ * If so, implement this method on your delegate and return NO. If you warn the user, you
+ * should hold onto the URL and once you have received their acknowledgement open the URL yourself
+ * using [[UIApplication sharedApplication] openURL:].
+ */
+- (BOOL)dialog:(FBDialog*)dialog shouldOpenURLInExternalBrowser:(NSURL *)url;
+
+@end
656 FBConnect/FBDialog.m
@@ -0,0 +1,656 @@
+/*
+ * Copyright 2010 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 "FBDialog.h"
+#import "Facebook.h"
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// global
+
+static NSString* kDefaultTitle = @"Connect to Facebook";
+
+static CGFloat kFacebookBlue[4] = {0.42578125, 0.515625, 0.703125, 1.0};
+static CGFloat kBorderGray[4] = {0.3, 0.3, 0.3, 0.8};
+static CGFloat kBorderBlack[4] = {0.3, 0.3, 0.3, 1};
+static CGFloat kBorderBlue[4] = {0.23, 0.35, 0.6, 1.0};
+
+static CGFloat kTransitionDuration = 0.3;
+
+static CGFloat kTitleMarginX = 8;
+static CGFloat kTitleMarginY = 4;
+static CGFloat kPadding = 10;
+static CGFloat kBorderWidth = 10;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+BOOL FBIsDeviceIPad() {
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 30200
+ if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
+ return YES;
+ }
+#endif
+ return NO;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation FBDialog
+
+@synthesize delegate = _delegate,
+ params = _params;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// private
+
+- (void)addRoundedRectToPath:(CGContextRef)context rect:(CGRect)rect radius:(float)radius {
+ CGContextBeginPath(context);
+ CGContextSaveGState(context);
+
+ if (radius == 0) {
+ CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect));
+ CGContextAddRect(context, rect);
+ } else {
+ rect = CGRectOffset(CGRectInset(rect, 0.5, 0.5), 0.5, 0.5);
+ CGContextTranslateCTM(context, CGRectGetMinX(rect)-0.5, CGRectGetMinY(rect)-0.5);
+ CGContextScaleCTM(context, radius, radius);
+ float fw = CGRectGetWidth(rect) / radius;
+ float fh = CGRectGetHeight(rect) / radius;
+
+ CGContextMoveToPoint(context, fw, fh/2);
+ CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
+ CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
+ CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
+ CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
+ }
+
+ CGContextClosePath(context);
+ CGContextRestoreGState(context);
+}
+
+- (void)drawRect:(CGRect)rect fill:(const CGFloat*)fillColors radius:(CGFloat)radius {
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+
+ if (fillColors) {
+ CGContextSaveGState(context);
+ CGContextSetFillColor(context, fillColors);
+ if (radius) {
+ [self addRoundedRectToPath:context rect:rect radius:radius];
+ CGContextFillPath(context);
+ } else {
+ CGContextFillRect(context, rect);
+ }
+ CGContextRestoreGState(context);
+ }
+
+ CGColorSpaceRelease(space);
+}
+
+- (void)strokeLines:(CGRect)rect stroke:(const CGFloat*)strokeColor {
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
+
+ CGContextSaveGState(context);
+ CGContextSetStrokeColorSpace(context, space);
+ CGContextSetStrokeColor(context, strokeColor);
+ CGContextSetLineWidth(context, 1.0);
+
+ {
+ CGPoint points[] = {{rect.origin.x+0.5, rect.origin.y-0.5},
+ {rect.origin.x+rect.size.width, rect.origin.y-0.5}};
+ CGContextStrokeLineSegments(context, points, 2);
+ }
+ {
+ CGPoint points[] = {{rect.origin.x+0.5, rect.origin.y+rect.size.height-0.5},
+ {rect.origin.x+rect.size.width-0.5, rect.origin.y+rect.size.height-0.5}};
+ CGContextStrokeLineSegments(context, points, 2);
+ }
+ {
+ CGPoint points[] = {{rect.origin.x+rect.size.width-0.5, rect.origin.y},
+ {rect.origin.x+rect.size.width-0.5, rect.origin.y+rect.size.height}};
+ CGContextStrokeLineSegments(context, points, 2);
+ }
+ {
+ CGPoint points[] = {{rect.origin.x+0.5, rect.origin.y},
+ {rect.origin.x+0.5, rect.origin.y+rect.size.height}};
+ CGContextStrokeLineSegments(context, points, 2);
+ }
+
+ CGContextRestoreGState(context);
+
+ CGColorSpaceRelease(space);
+}
+
+- (BOOL)shouldRotateToOrientation:(UIDeviceOrientation)orientation {
+ if (orientation == _orientation) {
+ return NO;
+ } else {
+ return orientation == UIDeviceOrientationLandscapeLeft
+ || orientation == UIDeviceOrientationLandscapeRight
+ || orientation == UIDeviceOrientationPortrait
+ || orientation == UIDeviceOrientationPortraitUpsideDown;
+ }
+}
+
+- (CGAffineTransform)transformForOrientation {
+ UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
+ if (orientation == UIInterfaceOrientationLandscapeLeft) {
+ return CGAffineTransformMakeRotation(M_PI*1.5);
+ } else if (orientation == UIInterfaceOrientationLandscapeRight) {
+ return CGAffineTransformMakeRotation(M_PI/2);
+ } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
+ return CGAffineTransformMakeRotation(-M_PI);
+ } else {
+ return CGAffineTransformIdentity;
+ }
+}
+
+- (void)sizeToFitOrientation:(BOOL)transform {
+ if (transform) {
+ self.transform = CGAffineTransformIdentity;
+ }
+
+ CGRect frame = [UIScreen mainScreen].applicationFrame;
+ CGPoint center = CGPointMake(
+ frame.origin.x + ceil(frame.size.width/2),
+ frame.origin.y + ceil(frame.size.height/2));
+
+ CGFloat scale_factor = 1.0f;
+ if (FBIsDeviceIPad()) {
+ // On the iPad the dialog's dimensions should only be 60% of the screen's
+ scale_factor = 0.6f;
+ }
+
+ CGFloat width = floor(scale_factor * frame.size.width) - kPadding * 2;
+ CGFloat height = floor(scale_factor * frame.size.height) - kPadding * 2;
+
+ _orientation = [UIApplication sharedApplication].statusBarOrientation;
+ if (UIInterfaceOrientationIsLandscape(_orientation)) {
+ self.frame = CGRectMake(kPadding, kPadding, height, width);
+ } else {
+ self.frame = CGRectMake(kPadding, kPadding, width, height);
+ }
+ self.center = center;
+
+ if (transform) {
+ self.transform = [self transformForOrientation];
+ }
+}
+
+- (void)updateWebOrientation {
+ UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
+ if (UIInterfaceOrientationIsLandscape(orientation)) {
+ [_webView stringByEvaluatingJavaScriptFromString:
+ @"document.body.setAttribute('orientation', 90);"];
+ } else {
+ [_webView stringByEvaluatingJavaScriptFromString:
+ @"document.body.removeAttribute('orientation');"];
+ }
+}
+
+- (void)bounce1AnimationStopped {
+ [UIView beginAnimations:nil context:nil];
+ [UIView setAnimationDuration:kTransitionDuration/2];
+ [UIView setAnimationDelegate:self];
+ [UIView setAnimationDidStopSelector:@selector(bounce2AnimationStopped)];
+ self.transform = CGAffineTransformScale([self transformForOrientation], 0.9, 0.9);
+ [UIView commitAnimations];
+}
+
+- (void)bounce2AnimationStopped {
+ [UIView beginAnimations:nil context:nil];
+ [UIView setAnimationDuration:kTransitionDuration/2];
+ self.transform = [self transformForOrientation];
+ [UIView commitAnimations];
+}
+
+- (NSURL*)generateURL:(NSString*)baseURL params:(NSDictionary*)params {
+ if (params) {
+ NSMutableArray* pairs = [NSMutableArray array];
+ for (NSString* key in params.keyEnumerator) {
+ NSString* value = [params objectForKey:key];
+ NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(
+ NULL, /* allocator */
+ (CFStringRef)value,
+ NULL, /* charactersToLeaveUnescaped */
+ (CFStringRef)@"!*'();:@&=+$,/?%#[]",
+ kCFStringEncodingUTF8);
+
+ [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, escaped_value]];
+ [escaped_value release];
+ }
+
+ NSString* query = [pairs componentsJoinedByString:@"&"];
+ NSString* url = [NSString stringWithFormat:@"%@?%@", baseURL, query];
+ return [NSURL URLWithString:url];
+ } else {
+ return [NSURL URLWithString:baseURL];
+ }
+}
+
+- (void)addObservers {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(deviceOrientationDidChange:)
+ name:@"UIDeviceOrientationDidChangeNotification" object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(keyboardWillShow:) name:@"UIKeyboardWillShowNotification" object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(keyboardWillHide:) name:@"UIKeyboardWillHideNotification" object:nil];
+}
+
+- (void)removeObservers {
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:@"UIDeviceOrientationDidChangeNotification" object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:@"UIKeyboardWillShowNotification" object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:@"UIKeyboardWillHideNotification" object:nil];
+}
+
+- (void)postDismissCleanup {
+ [self removeObservers];
+ [self removeFromSuperview];
+ [_modalBackgroundView removeFromSuperview];
+}
+
+- (void)dismiss:(BOOL)animated {
+ [self dialogWillDisappear];
+
+ [_loadingURL release];
+ _loadingURL = nil;
+
+ if (animated) {
+ [UIView beginAnimations:nil context:nil];
+ [UIView setAnimationDuration:kTransitionDuration];
+ [UIView setAnimationDelegate:self];
+ [UIView setAnimationDidStopSelector:@selector(postDismissCleanup)];
+ self.alpha = 0;
+ [UIView commitAnimations];
+ } else {
+ [self postDismissCleanup];
+ }
+}
+
+- (void)cancel {
+ [self dialogDidCancel:nil];
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// NSObject
+
+- (id)init {
+ if (self = [super initWithFrame:CGRectZero]) {
+ _delegate = nil;
+ _loadingURL = nil;
+ _orientation = UIDeviceOrientationUnknown;
+ _showingKeyboard = NO;
+
+ self.backgroundColor = [UIColor clearColor];
+ self.autoresizesSubviews = YES;
+ self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ self.contentMode = UIViewContentModeRedraw;
+
+ UIImage* iconImage = [UIImage imageNamed:@"FBDialog.bundle/images/fbicon.png"];
+ UIImage* closeImage = [UIImage imageNamed:@"FBDialog.bundle/images/close.png"];
+
+ _iconView = [[UIImageView alloc] initWithImage:iconImage];
+ [self addSubview:_iconView];
+
+ UIColor* color = [UIColor colorWithRed:167.0/255 green:184.0/255 blue:216.0/255 alpha:1];
+ _closeButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
+ [_closeButton setImage:closeImage forState:UIControlStateNormal];
+ [_closeButton setTitleColor:color forState:UIControlStateNormal];
+ [_closeButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
+ [_closeButton addTarget:self action:@selector(cancel)
+ forControlEvents:UIControlEventTouchUpInside];
+
+ // To be compatible with OS 2.x
+ #if __IPHONE_OS_VERSION_MAX_ALLOWED <= __IPHONE_2_2
+ _closeButton.font = [UIFont boldSystemFontOfSize:12];
+ #else
+ _closeButton.titleLabel.font = [UIFont boldSystemFontOfSize:12];
+ #endif
+
+ _closeButton.showsTouchWhenHighlighted = YES;
+ _closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin
+ | UIViewAutoresizingFlexibleBottomMargin;
+ [self addSubview:_closeButton];
+
+ CGFloat titleLabelFontSize = (FBIsDeviceIPad() ? 18 : 14);
+ _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
+ _titleLabel.text = kDefaultTitle;
+ _titleLabel.backgroundColor = [UIColor clearColor];
+ _titleLabel.textColor = [UIColor whiteColor];
+ _titleLabel.font = [UIFont boldSystemFontOfSize:titleLabelFontSize];
+ _titleLabel.autoresizingMask = UIViewAutoresizingFlexibleRightMargin
+ | UIViewAutoresizingFlexibleBottomMargin;
+ [self addSubview:_titleLabel];
+
+ _webView = [[UIWebView alloc] initWithFrame:CGRectMake(kPadding, kPadding, 480, 480)];
+ _webView.delegate = self;
+ _webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ [self addSubview:_webView];
+
+ _spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:
+ UIActivityIndicatorViewStyleWhiteLarge];
+ _spinner.autoresizingMask =
+ UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin
+ | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
+ [self addSubview:_spinner];
+ _modalBackgroundView = [[UIView alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ _webView.delegate = nil;
+ [_webView release];
+ [_params release];
+ [_serverURL release];
+ [_spinner release];
+ [_titleLabel release];
+ [_iconView release];
+ [_closeButton release];
+ [_loadingURL release];
+ [_modalBackgroundView release];
+ [super dealloc];
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// UIView
+
+- (void)drawRect:(CGRect)rect {
+ CGRect grayRect = CGRectOffset(rect, -0.5, -0.5);
+ [self drawRect:grayRect fill:kBorderGray radius:10];
+
+ CGRect headerRect = CGRectMake(
+ ceil(rect.origin.x + kBorderWidth), ceil(rect.origin.y + kBorderWidth),
+ rect.size.width - kBorderWidth*2, _titleLabel.frame.size.height);
+ [self drawRect:headerRect fill:kFacebookBlue radius:0];
+ [self strokeLines:headerRect stroke:kBorderBlue];
+
+ CGRect webRect = CGRectMake(
+ ceil(rect.origin.x + kBorderWidth), headerRect.origin.y + headerRect.size.height,
+ rect.size.width - kBorderWidth*2, _webView.frame.size.height+1);
+ [self strokeLines:webRect stroke:kBorderBlack];
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// UIWebViewDelegate
+
+- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
+ navigationType:(UIWebViewNavigationType)navigationType {
+ NSURL* url = request.URL;
+
+ if ([url.scheme isEqualToString:@"fbconnect"]) {
+ if ([[url.resourceSpecifier substringToIndex:8] isEqualToString:@"//cancel"]) {
+ NSString * errorCode = [self getStringFromUrl:[url absoluteString] needle:@"error_code="];
+ NSString * errorStr = [self getStringFromUrl:[url absoluteString] needle:@"error_msg="];
+ if (errorCode) {
+ NSDictionary * errorData = [NSDictionary dictionaryWithObject:errorStr forKey:@"error_msg"];
+ NSError * error = [NSError errorWithDomain:@"facebookErrDomain"
+ code:[errorCode intValue]
+ userInfo:errorData];
+ [self dismissWithError:error animated:YES];
+ } else {
+ [self dialogDidCancel:url];
+ }
+ } else {
+ [self dialogDidSucceed:url];
+ }
+ return NO;
+ } else if ([_loadingURL isEqual:url]) {
+ return YES;
+ } else if (navigationType == UIWebViewNavigationTypeLinkClicked) {
+ if ([_delegate respondsToSelector:@selector(dialog:shouldOpenURLInExternalBrowser:)]) {
+ if (![_delegate dialog:self shouldOpenURLInExternalBrowser:url]) {
+ return NO;
+ }
+ }
+
+ [[UIApplication sharedApplication] openURL:request.URL];
+ return NO;
+ } else {
+ return YES;
+ }
+}
+
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [_spinner stopAnimating];
+ _spinner.hidden = YES;
+
+ self.title = [_webView stringByEvaluatingJavaScriptFromString:@"document.title"];
+ [self updateWebOrientation];
+}
+
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ // 102 == WebKitErrorFrameLoadInterruptedByPolicyChange
+ if (!([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102)) {
+ [self dismissWithError:error animated:YES];
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// UIDeviceOrientationDidChangeNotification
+
+- (void)deviceOrientationDidChange:(void*)object {
+ UIDeviceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
+ if (!_showingKeyboard && [self shouldRotateToOrientation:orientation]) {
+ [self updateWebOrientation];
+
+ CGFloat duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration;
+ [UIView beginAnimations:nil context:nil];
+ [UIView setAnimationDuration:duration];
+ [self sizeToFitOrientation:YES];
+ [UIView commitAnimations];
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// UIKeyboardNotifications
+
+- (void)keyboardWillShow:(NSNotification*)notification {
+
+ _showingKeyboard = YES;
+
+ if (FBIsDeviceIPad()) {
+ // On the iPad the screen is large enough that we don't need to
+ // resize the dialog to accomodate the keyboard popping up
+ return;
+ }
+
+ UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
+ if (UIInterfaceOrientationIsLandscape(orientation)) {
+ _webView.frame = CGRectInset(_webView.frame,
+ -(kPadding + kBorderWidth),
+ -(kPadding + kBorderWidth) - _titleLabel.frame.size.height);
+ }
+}
+
+- (void)keyboardWillHide:(NSNotification*)notification {
+ _showingKeyboard = NO;
+
+ if (FBIsDeviceIPad()) {
+ return;
+ }
+ UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
+ if (UIInterfaceOrientationIsLandscape(orientation)) {
+ _webView.frame = CGRectInset(_webView.frame,
+ kPadding + kBorderWidth,
+ kPadding + kBorderWidth + _titleLabel.frame.size.height);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// public
+
+/**
+ * Find a specific parameter from the url
+ */
+- (NSString *) getStringFromUrl: (NSString*) url needle:(NSString *) needle {
+ NSString * str = nil;
+ NSRange start = [url rangeOfString:needle];
+ if (start.location != NSNotFound) {
+ NSRange end = [[url substringFromIndex:start.location+start.length] rangeOfString:@"&"];
+ NSUInteger offset = start.location+start.length;
+ str = end.location == NSNotFound
+ ? [url substringFromIndex:offset]
+ : [url substringWithRange:NSMakeRange(offset, end.location)];
+ str = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ }
+
+ return str;
+}
+
+- (id)initWithURL: (NSString *) serverURL
+ params: (NSMutableDictionary *) params
+ delegate: (id <FBDialogDelegate>) delegate {
+
+ self = [self init];
+ _serverURL = [serverURL retain];
+ _params = [params retain];
+ _delegate = delegate;
+
+ return self;
+}
+
+- (NSString*)title {
+ return _titleLabel.text;
+}
+
+- (void)setTitle:(NSString*)title {
+ _titleLabel.text = title;
+}
+
+- (void)load {
+ [self loadURL:_serverURL get:_params];
+}
+
+- (void)loadURL:(NSString*)url get:(NSDictionary*)getParams {
+
+ [_loadingURL release];
+ _loadingURL = [[self generateURL:url params:getParams] retain];
+ NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:_loadingURL];
+
+ [_webView loadRequest:request];
+}
+
+- (void)show {
+ [self load];
+ [self sizeToFitOrientation:NO];
+
+ CGFloat innerWidth = self.frame.size.width - (kBorderWidth+1)*2;
+ [_iconView sizeToFit];
+ [_titleLabel sizeToFit];
+ [_closeButton sizeToFit];
+
+ _titleLabel.frame = CGRectMake(
+ kBorderWidth + kTitleMarginX + _iconView.frame.size.width + kTitleMarginX,
+ kBorderWidth,
+ innerWidth - (_titleLabel.frame.size.height + _iconView.frame.size.width + kTitleMarginX*2),
+ _titleLabel.frame.size.height + kTitleMarginY*2);
+
+ _iconView.frame = CGRectMake(
+ kBorderWidth + kTitleMarginX,
+ kBorderWidth + floor(_titleLabel.frame.size.height/2 - _iconView.frame.size.height/2),
+ _iconView.frame.size.width,
+ _iconView.frame.size.height);
+
+ _closeButton.frame = CGRectMake(
+ self.frame.size.width - (_titleLabel.frame.size.height + kBorderWidth),
+ kBorderWidth,
+ _titleLabel.frame.size.height,
+ _titleLabel.frame.size.height);
+
+ _webView.frame = CGRectMake(
+ kBorderWidth+1,
+ kBorderWidth + _titleLabel.frame.size.height,
+ innerWidth,
+ self.frame.size.height - (_titleLabel.frame.size.height + 1 + kBorderWidth*2));
+
+ [_spinner sizeToFit];
+ [_spinner startAnimating];
+ _spinner.center = _webView.center;
+
+ UIWindow* window = [UIApplication sharedApplication].keyWindow;
+ if (!window) {
+ window = [[UIApplication sharedApplication].windows objectAtIndex:0];
+ }
+
+ _modalBackgroundView.frame = window.frame;
+ [_modalBackgroundView addSubview:self];
+ [window addSubview:_modalBackgroundView];
+
+ [window addSubview:self];
+
+ [self dialogWillAppear];
+
+ self.transform = CGAffineTransformScale([self transformForOrientation], 0.001, 0.001);
+ [UIView beginAnimations:nil context:nil];
+ [UIView setAnimationDuration:kTransitionDuration/1.5];
+ [UIView setAnimationDelegate:self];
+ [UIView setAnimationDidStopSelector:@selector(bounce1AnimationStopped)];
+ self.transform = CGAffineTransformScale([self transformForOrientation], 1.1, 1.1);
+ [UIView commitAnimations];
+
+ [self addObservers];
+}
+
+- (void)dismissWithSuccess:(BOOL)success animated:(BOOL)animated {
+ if (success) {
+ if ([_delegate respondsToSelector:@selector(dialogDidComplete:)]) {
+ [_delegate dialogDidComplete:self];
+ }
+ } else {
+ if ([_delegate respondsToSelector:@selector(dialogDidNotComplete:)]) {
+ [_delegate dialogDidNotComplete:self];
+ }
+ }
+
+ [self dismiss:animated];
+}
+
+- (void)dismissWithError:(NSError*)error animated:(BOOL)animated {
+ if ([_delegate respondsToSelector:@selector(dialog:didFailWithError:)]) {
+ [_delegate dialog:self didFailWithError:error];
+ }
+
+ [self dismiss:animated];
+}
+
+- (void)dialogWillAppear {
+}
+
+- (void)dialogWillDisappear {
+}
+
+- (void)dialogDidSucceed:(NSURL *)url {
+
+ if ([_delegate respondsToSelector:@selector(dialogCompleteWithUrl:)]) {
+ [_delegate dialogCompleteWithUrl:url];
+ }
+ [self dismissWithSuccess:YES animated:YES];
+}
+
+- (void)dialogDidCancel:(NSURL *)url {
+ if ([_delegate respondsToSelector:@selector(dialogDidNotCompleteWithUrl:)]) {
+ [_delegate dialogDidNotCompleteWithUrl:url];
+ }
+ [self dismissWithSuccess:NO animated:YES];
+}
+
+@end
48 FBConnect/FBLoginDialog.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2010 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 "FBDialog.h"
+
+@protocol FBLoginDialogDelegate;
+
+/**
+ * Do not use this interface directly, instead, use authorize in Facebook.h
+ *
+ * Facebook Login Dialog interface for start the facebook webView login dialog.
+ * It start pop-ups prompting for credentials and permissions.
+ */
+
+@interface FBLoginDialog : FBDialog {
+ id<FBLoginDialogDelegate> _loginDelegate;
+}
+
+-(id) initWithURL:(NSString *) loginURL
+ loginParams:(NSMutableDictionary *) params
+ delegate:(id <FBLoginDialogDelegate>) delegate;
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol FBLoginDialogDelegate <NSObject>
+
+- (void)fbDialogLogin:(NSString*)token expirationDate:(NSDate*)expirationDate;
+
+- (void)fbDialogNotLogin:(BOOL)cancelled;
+
+@end
+
+
94 FBConnect/FBLoginDialog.m
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2010 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 "FBDialog.h"
+#import "FBLoginDialog.h"
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation FBLoginDialog
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// public
+
+/*
+ * initialize the FBLoginDialog with url and parameters
+ */
+- (id)initWithURL:(NSString*) loginURL
+ loginParams:(NSMutableDictionary*) params
+ delegate:(id <FBLoginDialogDelegate>) delegate{
+
+ self = [super init];
+ _serverURL = [loginURL retain];
+ _params = [params retain];
+ _loginDelegate = delegate;
+ return self;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// FBDialog
+
+/**
+ * Override FBDialog : to call when the webView Dialog did succeed
+ */
+- (void) dialogDidSucceed:(NSURL*)url {
+ NSString *q = [url absoluteString];
+ NSString *token = [self getStringFromUrl:q needle:@"access_token="];
+ NSString *expTime = [self getStringFromUrl:q needle:@"expires_in="];
+ NSDate *expirationDate =nil;
+
+ if (expTime != nil) {
+ int expVal = [expTime intValue];
+ if (expVal == 0) {
+ expirationDate = [NSDate distantFuture];
+ } else {
+ expirationDate = [NSDate dateWithTimeIntervalSinceNow:expVal];
+ }
+ }
+
+ if ((token == (NSString *) [NSNull null]) || (token.length == 0)) {
+ [self dialogDidCancel:url];
+ [self dismissWithSuccess:NO animated:YES];
+ } else {
+ if ([_loginDelegate respondsToSelector:@selector(fbDialogLogin:expirationDate:)]) {
+ [_loginDelegate fbDialogLogin:token expirationDate:expirationDate];
+ }
+ [self dismissWithSuccess:YES animated:YES];
+ }
+
+}
+
+/**
+ * Override FBDialog : to call with the login dialog get canceled
+ */
+- (void)dialogDidCancel:(NSURL *)url {
+ [self dismissWithSuccess:NO animated:YES];
+ if ([_loginDelegate respondsToSelector:@selector(fbDialogNotLogin:)]) {
+ [_loginDelegate fbDialogNotLogin:YES];
+ }
+}
+
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ if (!(([error.domain isEqualToString:@"NSURLErrorDomain"] && error.code == -999) ||
+ ([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102))) {
+ [super webView:webView didFailLoadWithError:error];
+ if ([_loginDelegate respondsToSelector:@selector(fbDialogNotLogin:)]) {
+ [_loginDelegate fbDialogNotLogin:NO];
+ }
+ }
+}
+
+@end
116 FBConnect/FBRequest.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2010 Facebook
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+@protocol FBRequestDelegate;
+
+/**
+ * Do not use this interface directly, instead, use method in Facebook.h
+ */
+@interface FBRequest : NSObject {
+ id<FBRequestDelegate> _delegate;
+ NSString* _url;
+ NSString* _httpMethod;
+ NSMutableDictionary* _params;
+ NSURLConnection* _connection;
+ NSMutableData* _responseText;
+}
+
+
+@property(nonatomic,assign) id<FBRequestDelegate> delegate;
+
+/**
+ * The URL which will be contacted to execute the request.
+ */
+@property(nonatomic,copy) NSString* url;
+
+/**
+ * The API method which will be called.
+ */
+@property(nonatomic,copy) NSString* httpMethod;
+
+/**
+ * The dictionary of parameters to pass to the method.
+ *
+ * These values in the dictionary will be converted to strings using the
+ * standard Objective-C object-to-string conversion facilities.
+ */
+@property(nonatomic,retain) NSMutableDictionary* params;
+@property(nonatomic,assign) NSURLConnection* connection;
+@property(nonatomic,assign) NSMutableData* responseText;
+
+
++ (NSString*)serializeURL:(NSString *)baseUrl
+ params:(NSDictionary *)params;
+
++ (NSString*)serializeURL:(NSString *)baseUrl
+ params:(NSDictionary *)params
+ httpMethod:(NSString *)httpMethod;
+
++ (FBRequest*)getRequestWithParams:(NSMutableDictionary *) params
+ httpMethod:(NSString *) httpMethod
+ delegate:(id<FBRequestDelegate>)delegate
+ requestURL:(NSString *) url;
+- (BOOL) loading;
+
+- (void) connect;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////
+
+/*
+ *Your application should implement this delegate
+ */
+@protocol FBRequestDelegate <NSObject>
+
+@optional
+
+/**
+ * Called just before the request is sent to the server.
+ */
+- (void)requestLoading:(FBRequest *)request;
+
+/**
+ * Called when the server responds and begins to send back data.
+ */
+- (void)request:(FBRequest *)request didReceiveResponse:(NSURLResponse *)response;
+
+/**
+ * Called when an error prevents the request from completing successfully.
+ */
+- (void)request:(FBRequest *)request didFailWithError:(NSError *)error;
+
+/**
+ * Called when a request returns and its response has been parsed into
+ * an object.
+ *
+ * The resulting object may be a dictionary, an array, a string, or a number,
+ * depending on thee format of the API response.
+ */
+- (void)request:(FBRequest *)request didLoad:(id)result;
+
+/**
+ * Called when a request returns a response.
+ *
+ * The result object is the raw response from the server of type NSData
+ */
+- (void)request:(FBRequest *)request didLoadRawResponse:(NSData *)data;
+
+@end
+
363 FBConnect/FBRequest.m
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2010 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 "FBRequest.h"
+#import "JSON.h"
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// global
+
+static NSString* kUserAgent = @"FacebookConnect";
+static NSString* kStringBoundary = @"3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f";
+static const int kGeneralErrorCode = 10000;
+
+static const NSTimeInterval kTimeoutInterval = 180.0;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation FBRequest
+
+@synthesize delegate = _delegate,
+ url = _url,
+ httpMethod = _httpMethod,
+ params = _params,
+ connection = _connection,
+ responseText = _responseText;
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// class public
+
++ (FBRequest *)getRequestWithParams:(NSMutableDictionary *) params
+ httpMethod:(NSString *) httpMethod
+ delegate:(id<FBRequestDelegate>) delegate
+ requestURL:(NSString *) url {
+
+ FBRequest* request = [[[FBRequest alloc] init] autorelease];
+ request.delegate = delegate;
+ request.url = url;
+ request.httpMethod = httpMethod;
+ request.params = params;
+ request.connection = nil;
+ request.responseText = nil;
+
+ return request;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// private
+
++ (NSString *)serializeURL:(NSString *)baseUrl
+ params:(NSDictionary *)params {
+ return [self serializeURL:baseUrl params:params httpMethod:@"GET"];
+}
+
+/**
+ * Generate get URL
+ */
++ (NSString*)serializeURL:(NSString *)baseUrl
+ params:(NSDictionary *)params
+ httpMethod:(NSString *)httpMethod {
+
+ NSURL* parsedURL = [NSURL URLWithString:baseUrl];
+ NSString* queryPrefix = parsedURL.query ? @"&" : @"?";
+
+ NSMutableArray* pairs = [NSMutableArray array];
+ for (NSString* key in [params keyEnumerator]) {
+ if (([[params valueForKey:key] isKindOfClass:[UIImage class]])
+ ||([[params valueForKey:key] isKindOfClass:[NSData class]])) {
+ if ([httpMethod isEqualToString:@"GET"]) {
+ NSLog(@"can not use GET to upload a file");
+ }
+ continue;
+ }
+
+ NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(
+ NULL, /* allocator */
+ (CFStringRef)[params objectForKey:key],
+ NULL, /* charactersToLeaveUnescaped */
+ (CFStringRef)@"!*'();:@&=+$,/?%#[]",
+ kCFStringEncodingUTF8);
+
+ [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, escaped_value]];
+ [escaped_value release];
+ }
+ NSString* query = [pairs componentsJoinedByString:@"&"];
+
+ return [NSString stringWithFormat:@"%@%@%@", baseUrl, queryPrefix, query];
+}
+
+/**
+ * Body append for POST method
+ */
+- (void)utfAppendBody:(NSMutableData *)body data:(NSString *)data {
+ [body appendData:[data dataUsingEncoding:NSUTF8StringEncoding]];
+}
+
+/**
+ * Generate body for POST method
+ */
+- (NSMutableData *)generatePostBody {
+ NSMutableData *body = [NSMutableData data];
+ NSString *endLine = [NSString stringWithFormat:@"\r\n--%@\r\n", kStringBoundary];
+ NSMutableDictionary *dataDictionary = [NSMutableDictionary dictionary];
+
+ [self utfAppendBody:body data:[NSString stringWithFormat:@"--%@\r\n", kStringBoundary]];
+
+ for (id key in [_params keyEnumerator]) {
+
+ if (([[_params valueForKey:key] isKindOfClass:[UIImage class]])
+ ||([[_params valueForKey:key] isKindOfClass:[NSData class]])) {
+
+ [dataDictionary setObject:[_params valueForKey:key] forKey:key];
+ continue;
+
+ }
+
+ [self utfAppendBody:body
+ data:[NSString
+ stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",
+ key]];
+ [self utfAppendBody:body data:[_params valueForKey:key]];
+
+ [self utfAppendBody:body data:endLine];
+ }
+
+ if ([dataDictionary count] > 0) {
+ for (id key in dataDictionary) {
+ NSObject *dataParam = [dataDictionary valueForKey:key];
+ if ([dataParam isKindOfClass:[UIImage class]]) {
+ NSData* imageData = UIImagePNGRepresentation((UIImage*)dataParam);
+ [self utfAppendBody:body
+ data:[NSString stringWithFormat:
+ @"Content-Disposition: form-data; filename=\"%@\"\r\n", key]];
+ [self utfAppendBody:body
+ data:[NSString stringWithString:@"Content-Type: image/png\r\n\r\n"]];
+ [body appendData:imageData];
+ } else {
+ NSAssert([dataParam isKindOfClass:[NSData class]],
+ @"dataParam must be a UIImage or NSData");
+ [self utfAppendBody:body
+ data:[NSString stringWithFormat:
+ @"Content-Disposition: form-data; filename=\"%@\"\r\n", key]];
+ [self utfAppendBody:body
+ data:[NSString stringWithString:@"Content-Type: content/unknown\r\n\r\n"]];
+ [body appendData:(NSData*)dataParam];
+ }
+ [self utfAppendBody:body data:endLine];
+
+ }
+ }
+
+ return body;
+}
+
+/**
+ * Formulate the NSError
+ */
+- (id)formError:(NSInteger)code userInfo:(NSDictionary *) errorData {
+ return [NSError errorWithDomain:@"facebookErrDomain" code:code userInfo:errorData];
+
+}
+
+/**
+ * parse the response data
+ */
+- (id)parseJsonResponse:(NSData *)data error:(NSError **)error {
+
+ NSString* responseString = [[[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding]
+ autorelease];
+ SBJSON *jsonParser = [[SBJSON new] autorelease];
+ if ([responseString isEqualToString:@"true"]) {
+ return [NSDictionary dictionaryWithObject:@"true" forKey:@"result"];
+ } else if ([responseString isEqualToString:@"false"]) {
+ if (error != nil) {
+ *error = [self formError:kGeneralErrorCode
+ userInfo:[NSDictionary
+ dictionaryWithObject:@"This operation can not be completed"
+ forKey:@"error_msg"]];
+ }
+ return nil;
+ }
+
+
+ id result = [jsonParser objectWithString:responseString];
+
+ if (![result isKindOfClass:[NSArray class]]) {
+ if ([result objectForKey:@"error"] != nil) {
+ if (error != nil) {
+ *error = [self formError:kGeneralErrorCode
+ userInfo:result];
+ }
+ return nil;
+ }
+
+ if ([result objectForKey:@"error_code"] != nil) {
+ if (error != nil) {
+ *error = [self formError:[[result objectForKey:@"error_code"] intValue] userInfo:result];
+ }
+ return nil;
+ }
+
+ if ([result objectForKey:@"error_msg"] != nil) {
+ if (error != nil) {
+ *error = [self formError:kGeneralErrorCode userInfo:result];
+ }
+ }
+
+ if ([result objectForKey:@"error_reason"] != nil) {
+ if (error != nil) {
+ *error = [self formError:kGeneralErrorCode userInfo:result];
+ }
+ }
+ }
+
+ return result;
+
+}
+
+/*
+ * private helper function: call the delegate function when the request
+ * fails with error
+ */
+- (void)failWithError:(NSError *)error {
+ if ([_delegate respondsToSelector:@selector(request:didFailWithError:)]) {
+ [_delegate request:self didFailWithError:error];
+ }
+}
+
+/*
+ * private helper function: handle the response data
+ */
+- (void)handleResponseData:(NSData *)data {
+ if ([_delegate respondsToSelector:
+ @selector(request:didLoadRawResponse:)]) {
+ [_delegate request:self didLoadRawResponse:data];
+ }
+
+ if ([_delegate respondsToSelector:@selector(request:didLoad:)] ||
+ [_delegate respondsToSelector:
+ @selector(request:didFailWithError:)]) {
+ NSError* error = nil;
+ id result = [self parseJsonResponse:data error:&error];
+
+ if (error) {
+ [self failWithError:error];
+ } else if ([_delegate respondsToSelector:
+ @selector(request:didLoad:)]) {
+ [_delegate request:self didLoad:(result == nil ? data : result)];
+ }
+
+ }
+
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// public
+
+/**
+ * @return boolean - whether this request is processing
+ */
+- (BOOL)loading {
+ return !!_connection;
+}
+
+/**
+ * make the Facebook request
+ */
+- (void)connect {
+
+ if ([_delegate respondsToSelector:@selector(requestLoading:)]) {
+ [_delegate requestLoading:self];
+ }
+
+ NSString* url = [[self class] serializeURL:_url params:_params httpMethod:_httpMethod];
+ NSMutableURLRequest* request =
+ [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:kTimeoutInterval];
+ [request setValue:kUserAgent forHTTPHeaderField:@"User-Agent"];
+
+
+ [request setHTTPMethod:self.httpMethod];
+ if ([self.httpMethod isEqualToString: @"POST"]) {
+ NSString* contentType = [NSString
+ stringWithFormat:@"multipart/form-data; boundary=%@", kStringBoundary];
+ [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
+
+ [request setHTTPBody:[self generatePostBody]];
+ }
+
+ _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
+
+}
+
+/**
+ * Free internal structure
+ */
+- (void)dealloc {
+ [_connection cancel];
+ [_connection release];
+ [_responseText release];
+ [_url release];
+ [_httpMethod release];
+ [_params release];
+ [super dealloc];
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// NSURLConnectionDelegate
+
+- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
+ _responseText = [[NSMutableData alloc] init];
+
+ NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
+ if ([_delegate respondsToSelector:
+ @selector(request:didReceiveResponse:)]) {
+ [_delegate request:self didReceiveResponse:httpResponse];
+ }
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
+ [_responseText appendData:data];
+}
+
+- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
+ willCacheResponse:(NSCachedURLResponse*)cachedResponse {
+ return nil;
+}
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
+ [self handleResponseData:_responseText];
+
+ [_responseText release];
+ _responseText = nil;
+ [_connection release];
+ _connection = nil;
+}
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
+ [self failWithError:error];
+
+ [_responseText release];
+ _responseText = nil;
+ [_connection release];
+ _connection = nil;
+}
+
+@end
113 FBConnect/Facebook.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2010 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 "FBLoginDialog.h"
+#import "FBRequest.h"
+
+@protocol FBSessionDelegate;
+
+/**
+ * Main Facebook interface for interacting with the Facebook developer API.
+ * Provides methods to log in and log out a user, make requests using the REST
+ * and Graph APIs, and start user interface interactions (such as
+ * pop-ups promoting for credentials, permissions, stream posts, etc.)
+ */
+@interface Facebook : NSObject<FBLoginDialogDelegate>{
+ NSString* _accessToken;
+ NSDate* _expirationDate;
+ id<FBSessionDelegate> _sessionDelegate;
+ FBRequest* _request;
+ FBDialog* _loginDialog;
+ FBDialog* _fbDialog;
+ NSString* _appId;
+ NSString* _localAppId;
+ NSArray* _permissions;
+}
+
+@property(nonatomic, copy) NSString* accessToken;
+@property(nonatomic, copy) NSDate* expirationDate;
+@property(nonatomic, assign) id<FBSessionDelegate> sessionDelegate;
+@property(nonatomic, copy) NSString* localAppId;
+
+- (id)initWithAppId:(NSString *)app_id;
+
+- (void)authorize:(NSArray *)permissions
+ delegate:(id<FBSessionDelegate>)delegate;
+
+- (void)authorize:(NSArray *)permissions
+ delegate:(id<FBSessionDelegate>)delegate
+ localAppId:(NSString *)localAppId;
+
+- (BOOL)handleOpenURL:(NSURL *)url;
+
+- (void)logout:(id<FBSessionDelegate>)delegate;
+
+- (FBRequest*)requestWithParams:(NSMutableDictionary *)params
+ andDelegate:(id <FBRequestDelegate>)delegate;
+
+- (FBRequest*)requestWithMethodName:(NSString *)methodName
+ andParams:(NSMutableDictionary *)params
+ andHttpMethod:(NSString *)httpMethod
+ andDelegate:(id <FBRequestDelegate>)delegate;
+
+- (FBRequest*)requestWithGraphPath:(NSString *)graphPath
+ andDelegate:(id <FBRequestDelegate>)delegate;
+
+- (FBRequest*)requestWithGraphPath:(NSString *)graphPath
+ andParams:(NSMutableDictionary *)params
+ andDelegate:(id <FBRequestDelegate>)delegate;
+
+- (FBRequest*)requestWithGraphPath:(NSString *)graphPath
+ andParams:(NSMutableDictionary *)params
+ andHttpMethod:(NSString *)httpMethod
+ andDelegate:(id <FBRequestDelegate>)delegate;
+
+- (void)dialog:(NSString *)action
+ andDelegate:(id<FBDialogDelegate>)delegate;
+
+- (void)dialog:(NSString *)action
+ andParams:(NSMutableDictionary *)params
+ andDelegate:(id <FBDialogDelegate>)delegate;
+
+- (BOOL)isSessionValid;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Your application should implement this delegate to receive session callbacks.
+ */
+@protocol FBSessionDelegate <NSObject>
+
+@optional
+
+/**
+ * Called when the user successfully logged in.
+ */
+- (void)fbDidLogin;
+
+/**
+ * Called when the user dismissed the dialog without logging in.
+ */
+- (void)fbDidNotLogin:(BOOL)cancelled;
+
+/**
+ * Called when the user logged out.
+ */
+- (void)fbDidLogout;
+
+@end
660 FBConnect/Facebook.m
@@ -0,0 +1,660 @@
+/*
+ * Copyright 2010 Facebook
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "Facebook.h"
+#import "FBLoginDialog.h"
+#import "FBRequest.h"
+
+static NSString* kDialogBaseURL = @"https://m.facebook.com/dialog/";
+static NSString* kGraphBaseURL = @"https://graph.facebook.com/";
+static NSString* kRestserverBaseURL = @"https://api.facebook.com/method/";
+
+static NSString* kFBAppAuthURLScheme = @"fbauth";
+static NSString* kFBAppAuthURLPath = @"authorize";
+static NSString* kRedirectURL = @"fbconnect://success";
+
+static NSString* kLogin = @"oauth";
+static NSString* kSDK = @"ios";
+static NSString* kSDKVersion = @"2";
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface Facebook ()
+
+// private properties
+@property(nonatomic, retain) NSArray* permissions;
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation Facebook
+
+@synthesize accessToken = _accessToken,
+ expirationDate = _expirationDate,
+ sessionDelegate = _sessionDelegate,
+ permissions = _permissions,
+ localAppId = _localAppId;
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// private
+
+
+/**
+ * Initialize the Facebook object with application ID.
+ */
+- (id)initWithAppId:(NSString *)app_id {
+ self = [super init];
+ if (self) {
+ [_appId release];
+ _appId = [app_id copy];
+ }
+ return self;
+}
+
+/**
+ * Override NSObject : free the space
+ */
+- (void)dealloc {
+ [_accessToken release];
+ [_expirationDate release];
+ [_request release];
+ [_loginDialog release];
+ [_fbDialog release];
+ [_appId release];
+ [_permissions release];
+ [_localAppId release];
+ [super dealloc];
+}
+
+/**
+ * A private helper function for sending HTTP requests.
+ *
+ * @param url
+ * url to send http request
+ * @param params
+ * parameters to append to the url
+ * @param httpMethod
+ * http method @"GET" or @"POST"
+ * @param delegate
+ * Callback interface for notifying the calling application when
+ * the request has received response
+ */
+- (FBRequest*)openUrl:(NSString *)url
+ params:(NSMutableDictionary *)params
+ httpMethod:(NSString *)httpMethod
+ delegate:(id<FBRequestDelegate>)delegate {
+
+ [params setValue:@"json" forKey:@"format"];
+ [params setValue:kSDK forKey:@"sdk"];
+ [params setValue:kSDKVersion forKey:@"sdk_version"];
+ if ([self isSessionValid]) {
+ [params setValue:self.accessToken forKey:@"access_token"];
+ }
+
+ [_request release];
+ _request = [[FBRequest getRequestWithParams:params
+ httpMethod:httpMethod
+ delegate:delegate
+ requestURL:url] retain];
+ [_request connect];
+ return _request;
+}
+
+/**
+ * A private function for getting the app's base url.
+ */
+- (NSString *)getOwnBaseUrl {
+ return [NSString stringWithFormat:@"fb%@%@://authorize",
+ _appId,
+ _localAppId ? _localAppId : @""];
+}
+
+/**
+ * A private function for opening the authorization dialog.
+ */
+- (void)authorizeWithFBAppAuth:(BOOL)tryFBAppAuth
+ safariAuth:(BOOL)trySafariAuth {
+ NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ _appId, @"client_id",
+ @"user_agent", @"type",
+ kRedirectURL, @"redirect_uri",
+ @"touch", @"display",
+ kSDKVersion, @"sdk",
+ nil];
+
+ NSString *loginDialogURL = [kDialogBaseURL stringByAppendingString:kLogin];
+
+ if (_permissions != nil) {
+ NSString* scope = [_permissions componentsJoinedByString:@","];
+ [params setValue:scope forKey:@"scope"];
+ }
+
+ if (_localAppId) {
+ [params setValue:_localAppId forKey:@"local_client_id"];
+ }
+
+ // If the device is running a version of iOS that supports multitasking,
+ // try to obtain the access token from the Facebook app installed
+ // on the device.
+ // If the Facebook app isn't installed or it doesn't support
+ // the fbauth:// URL scheme, fall back on Safari for obtaining the access token.
+ // This minimizes the chance that the user will have to enter his or
+ // her credentials in order to authorize the application.
+ BOOL didOpenOtherApp = NO;
+ UIDevice *device = [UIDevice currentDevice];
+ if ([device respondsToSelector:@selector(isMultitaskingSupported)] && [device isMultitaskingSupported]) {
+ if (tryFBAppAuth) {
+ NSString *scheme = kFBAppAuthURLScheme;
+ if (_localAppId) {
+ scheme = [scheme stringByAppendingString:@"2"];
+ }
+ NSString *urlPrefix = [NSString stringWithFormat:@"%@://%@", scheme, kFBAppAuthURLPath];
+ NSString *fbAppUrl = [FBRequest serializeURL:urlPrefix params:params];
+ didOpenOtherApp = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:fbAppUrl]];
+ }
+
+ if (trySafariAuth && !didOpenOtherApp) {
+ NSString *nextUrl = [self getOwnBaseUrl];
+ [params setValue:nextUrl forKey:@"redirect_uri"];
+
+ NSString *fbAppUrl = [FBRequest serializeURL:loginDialogURL params:params];
+ didOpenOtherApp = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:fbAppUrl]];
+ }
+ }
+
+ // If single sign-on failed, open an inline login dialog. This will require the user to
+ // enter his or her credentials.
+ if (!didOpenOtherApp) {
+ [_loginDialog release];
+ _loginDialog = [[FBLoginDialog alloc] initWithURL:loginDialogURL
+ loginParams:params
+ delegate:self];
+ [_loginDialog show];
+ }
+}
+
+/**
+ * A function for parsing URL parameters.
+ */
+- (NSDictionary*)parseURLParams:(NSString *)query {
+ NSArray *pairs = [query componentsSeparatedByString:@"&"];
+ NSMutableDictionary *params = [[[NSMutableDictionary alloc] init] autorelease];
+ for (NSString *pair in pairs) {
+ NSArray *kv = [pair componentsSeparatedByString:@"="];
+ NSString *val =
+ [[kv objectAtIndex:1]
+ stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+
+ [params setObject:val forKey:[kv objectAtIndex:0]];
+ }
+ return params;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//public
+
+- (void)authorize:(NSArray *)permissions
+ delegate:(id<FBSessionDelegate>)delegate {
+ [self authorize:permissions
+ delegate:delegate
+ localAppId:nil];
+}
+
+/**
+ * Starts a dialog which prompts the user to log in to Facebook and grant
+ * the requested permissions to the application.
+ *
+ * If the device supports multitasking, we use fast app switching to show
+ * the dialog in the Facebook app or, if the Facebook app isn't installed,
+ * in Safari (this enables single sign-on by allowing multiple apps on
+ * the device to share the same user session).
+ * When the user grants or denies the permissions, the app that
+ * showed the dialog (the Facebook app or Safari) redirects back to
+ * the calling application, passing in the URL the access token
+ * and/or any other parameters the Facebook backend includes in
+ * the result (such as an error code if an error occurs).
+ *
+ * See http://developers.facebook.com/docs/authentication/ for more details.
+ *
+ * Also note that requests may be made to the API without calling
+ * authorize() first, in which case only public information is returned.
+ *
+ * @param permissions
+ * A list of permission required for this application: e.g.
+ * "read_stream", "publish_stream", or "offline_access". see
+ * http://developers.facebook.com/docs/authentication/permissions
+ * This parameter should not be null -- if you do not require any
+ * permissions, then pass in an empty String array.
+ * @param delegate
+ * Callback interface for notifying the calling application when
+ * the user has logged in.
+ * @param localAppId
+ * localAppId is a string of lowercase letters that is
+ * appended to the base URL scheme used for SSO. For example,
+ * if your facebook ID is "350685531728" and you set localAppId to
+ * "abcd", the Facebook app will expect your application to bind to
+ * the following URL scheme: "fb350685531728abcd".
+ * This is useful if your have multiple iOS applications that
+ * share a single Facebook application id (for example, if you
+ * have a free and a paid version on the same app) and you want
+ * to use SSO with both apps. Giving both apps different
+ * localAppId values will allow the Facebook app to disambiguate
+ * their URL schemes and always redirect the user back to the
+ * correct app, even if both the free and the app is installed
+ * on the device.
+ * localAppId is supported on version 3.4.1 and above of the Facebook
+ * app. If the user has an older version of the Facebook app
+ * installed and your app uses localAppId parameter, the SDK will
+ * proceed as if the Facebook app isn't installed on the device
+ * and redirect the user to Safari.
+ */
+- (void)authorize:(NSArray *)permissions
+ delegate:(id<FBSessionDelegate>)delegate
+ localAppId:(NSString *)localAppId {
+ self.localAppId = localAppId;
+ self.permissions = permissions;
+
+ _sessionDelegate = delegate;
+
+ [self authorizeWithFBAppAuth:YES safariAuth:YES];
+}
+
+/**
+ * This function processes the URL the Facebook application or Safari used to
+ * open your application during a single sign-on flow.
+ *
+ * You MUST call this function in your UIApplicationDelegate's handleOpenURL
+ * method (see
+ * http://developer.apple.com/library/ios/#documentation/uikit/reference/UIApplicationDelegate_Protocol/Reference/Reference.html
+ * for more info).
+ *
+ * This will ensure that the authorization process will proceed smoothly once the
+ * Facebook application or Safari redirects back to your application.
+ *
+ * @param URL the URL that was passed to the application delegate's handleOpenURL method.
+ *
+ * @return YES if the URL starts with 'fb[app_id]://authorize and hence was handled
+ * by SDK, NO otherwise.
+ */
+- (BOOL)handleOpenURL:(NSURL *)url {
+ // If the URL's structure doesn't match the structure used for Facebook authorization, abort.
+ if (![[url absoluteString] hasPrefix:[self getOwnBaseUrl]]) {
+ return NO;
+ }
+
+ NSString *query = [url fragment];
+
+ // Version 3.2.3 of the Facebook app encodes the parameters in the query but
+ // version 3.3 and above encode the parameters in the fragment. To support
+ // both versions of the Facebook app, we try to parse the query if
+ // the fragment is missing.
+ if (!query) {
+ query = [url query];
+ }
+
+ NSDictionary *params = [self parseURLParams:query];
+ NSString *accessToken = [params valueForKey:@"access_token"];
+
+ // If the URL doesn't contain the access token, an error has occurred.
+ if (!accessToken) {
+ NSString *errorReason = [params valueForKey:@"error"];
+
+ // If the error response indicates that we should try again using Safari, open
+ // the authorization dialog in Safari.
+ if (errorReason && [errorReason isEqualToString:@"service_disabled_use_browser"]) {
+ [self authorizeWithFBAppAuth:NO safariAuth:YES];
+ return YES;
+ }
+
+ // If the error response indicates that we should try the authorization flow
+ // in an inline dialog, do that.
+ if (errorReason && [errorReason isEqualToString:@"service_disabled"]) {
+ [self authorizeWithFBAppAuth:NO safariAuth:NO];
+ return YES;
+ }
+
+ // The facebook app may return an error_code parameter in case it
+ // encounters a UIWebViewDelegate error. This should not be treated
+ // as a cancel.
+ NSString *errorCode = [params valueForKey:@"error_code"];
+
+ BOOL userDidCancel =
+ !errorCode && (!errorReason || [errorReason isEqualToString:@"access_denied"]);
+ [self fbDialogNotLogin:userDidCancel];
+ return YES;
+ }
+
+ // We have an access token, so parse the expiration date.
+ NSString *expTime = [params valueForKey:@"expires_in"];
+ NSDate *expirationDate = [NSDate distantFuture];
+ if (expTime != nil) {
+ int expVal = [expTime intValue];
+ if (expVal != 0) {
+ expirationDate = [NSDate dateWithTimeIntervalSinceNow:expVal];
+ }
+ }
+
+ [self fbDialogLogin:accessToken expirationDate:expirationDate];
+ return YES;
+}
+
+/**
+ * Invalidate the current user session by removing the access token in
+ * memory, clearing the browser cookie, and calling auth.expireSession
+ * through the API.
+ *
+ * Note that this method dosen't unauthorize the application --
+ * it just invalidates the access token. To unauthorize the application,
+ * the user must remove the app in the app settings page under the privacy
+ * settings screen on facebook.com.
+ *
+ * @param delegate
+ * Callback interface for notifying the calling application when
+ * the application has logged out
+ */
+- (void)logout:(id<FBSessionDelegate>)delegate {
+
+ _sessionDelegate = delegate;
+
+ NSMutableDictionary * params = [[NSMutableDictionary alloc] init];
+ [self requestWithMethodName:@"auth.expireSession"
+ andParams:params andHttpMethod:@"GET"
+ andDelegate:nil];
+
+ [params release];
+ [_accessToken release];
+ _accessToken = nil;
+ [_expirationDate release];
+ _expirationDate = nil;
+
+ NSHTTPCookieStorage* cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ NSArray* facebookCookies = [cookies cookiesForURL:
+ [NSURL URLWithString:@"http://login.facebook.com"]];
+
+ for (NSHTTPCookie* cookie in facebookCookies) {
+ [cookies deleteCookie:cookie];
+ }
+
+ if ([self.sessionDelegate respondsToSelector:@selector(fbDidLogout)]) {
+ [_sessionDelegate fbDidLogout];
+ }
+}
+
+/**
+ * Make a request to Facebook's REST API with the given
+ * parameters. One of the parameter keys must be "method" and its value
+ * should be a valid REST server API method.
+ *
+ * See http://developers.facebook.com/docs/reference/rest/
+ *
+ * @param parameters
+ * Key-value pairs of parameters to the request. Refer to the
+ * documentation: one of the parameters must be "method".
+ * @param delegate
+ * Callback interface for notifying the calling application when
+ * the request has received response
+ * @return FBRequest*
+ * Returns a pointer to the FBRequest object.
+ */
+- (FBRequest*)requestWithParams:(NSMutableDictionary *)params
+ andDelegate:(id <FBRequestDelegate>)delegate {
+ if ([params objectForKey:@"method"] == nil) {
+ NSLog(@"API Method must be specified");
+ return nil;
+ }
+
+ NSString * methodName = [params objectForKey:@"method"];
+ [params removeObjectForKey:@"method"];
+
+ return [self requestWithMethodName:methodName
+ andParams:params
+ andHttpMethod:@"GET"
+ andDelegate:delegate];
+}
+
+/**
+ * Make a request to Facebook's REST API with the given method name and
+ * parameters.
+ *
+ * See http://developers.facebook.com/docs/reference/rest/
+ *
+ *
+ * @param methodName
+ * a valid REST server API method.
+ * @param parameters
+ * Key-value pairs of parameters to the request. Refer to the
+ * documentation: one of the parameters must be "method". To upload
+ * a file, you should specify the httpMethod to be "POST" and the
+ * “params” you passed in should contain a value of the type
+ * (UIImage *) or (NSData *) which contains the content that you
+ * want to upload
+ * @param delegate
+ * Callback interface for notifying the calling application when
+ * the request has received response
+ * @return FBRequest*
+ * Returns a pointer to the FBRequest object.
+ */
+- (FBRequest*)requestWithMethodName:(NSString *)methodName
+ andParams:(NSMutableDictionary *)params
+ andHttpMethod:(NSString *)httpMethod
+ andDelegate:(id <FBRequestDelegate>)delegate {
+ NSString * fullURL = [kRestserverBaseURL stringByAppendingString:methodName];
+ return [self openUrl:fullURL
+ params:params
+ httpMethod:httpMethod
+ delegate:delegate];
+}
+
+/**
+ * Make a request to the Facebook Graph API without any parameters.
+ *
+ * See http://developers.facebook.com/docs/api
+ *
+ * @param graphPath
+ * Path to resource in the Facebook graph, e.g., to fetch data
+ * about the currently logged authenticated user, provide "me",
+ * which will fetch http://graph.facebook.com/me
+ * @param delegate
+ * Callback interface for notifying the calling application when
+ * the request has received response
+ * @return FBRequest*
+ * Returns a pointer to the FBRequest object.
+ */
+- (FBRequest*)requestWithGraphPath:(NSString *)graphPath
+ andDelegate:(id <FBRequestDelegate>)delegate {
+
+ return [self requestWithGraphPath:graphPath
+ andParams:[NSMutableDictionary dictionary]
+ andHttpMethod:@"GET"
+ andDelegate:delegate];
+}
+
+/**
+ * Make a request to the Facebook Graph API with the given string
+ * parameters using an HTTP GET (default method).
+ *
+ * See http://developers.facebook.com/docs/api
+ *
+ *
+ * @param graphPath
+ * Path to resource in the Facebook graph, e.g., to fetch data
+ * about the currently logged authenticated user, provide "me",
+ * which will fetch http://graph.facebook.com/me
+ * @param parameters
+ * key-value string parameters, e.g. the path "search" with
+ * parameters "q" : "facebook" would produce a query for the
+ * following graph resource:
+ * https://graph.facebook.com/search?q=facebook
+ * @param delegate
+ * Callback interface for notifying the calling application when
+ * the request has received response
+ * @return FBRequest*
+ * Returns a pointer to the FBRequest object.
+ */
+- (FBRequest*)requestWithGraphPath:(NSString *)graphPath
+ andParams:(NSMutableDictionary *)params
+ andDelegate:(id <FBRequestDelegate>)delegate {
+
+ return [self requestWithGraphPath:graphPath
+ andParams:params
+ andHttpMethod:@"GET"
+ andDelegate:delegate];
+}
+
+/**
+ * Make a request to the Facebook Graph API with the given
+ * HTTP method and string parameters. Note that binary data parameters
+ * (e.g. pictures) are not yet supported by this helper function.
+ *
+ * See http://developers.facebook.com/docs/api
+ *
+ *
+ * @param graphPath
+ * Path to resource in the Facebook graph, e.g., to fetch data
+ * about the currently logged authenticated user, provide "me",
+ * which will fetch http://graph.facebook.com/me
+ * @param parameters
+ * key-value string parameters, e.g. the path "search" with
+ * parameters {"q" : "facebook"} would produce a query for the
+ * following graph resource:
+ * https://graph.facebook.com/search?q=facebook
+ * To upload a file, you should specify the httpMethod to be
+ * "POST" and the “params” you passed in should contain a value
+ * of the type (UIImage *) or (NSData *) which contains the
+ * content that you want to upload
+ * @param httpMethod
+ * http verb, e.g. "GET", "POST", "DELETE"
+ * @param delegate
+ * Callback interface for notifying the calling application when
+ * the request has received response
+ * @return FBRequest*
+ * Returns a pointer to the FBRequest object.
+ */
+- (FBRequest*)requestWithGraphPath:(NSString *)graphPath
+ andParams:(NSMutableDictionary *)params
+ andHttpMethod:(NSString *)httpMethod
+ andDelegate:(id <FBRequestDelegate>)delegate {
+
+ NSString * fullURL = [kGraphBaseURL stringByAppendingString:graphPath];
+ return [self openUrl:fullURL
+ params:params
+ httpMethod:httpMethod
+ delegate:delegate];
+}
+
+/**
+ * Generate a UI dialog for the request action.
+ *
+ * @param action
+ * String representation of the desired method: e.g. "login",
+ * "feed", ...
+ * @param delegate
+ * Callback interface to notify the calling application when the
+ * dialog has completed.
+ */
+- (void)dialog:(NSString *)action
+ andDelegate:(id<FBDialogDelegate>)delegate {
+ NSMutableDictionary * params = [NSMutableDictionary dictionary];
+ [self dialog:action andParams:params andDelegate:delegate];
+}
+
+/**
+ * Generate a UI dialog for the request action with the provided parameters.
+ *
+ * @param action
+ * String representation of the desired method: e.g. "login",
+ * "feed", ...
+ * @param parameters
+ * key-value string parameters
+ * @param delegate
+ * Callback interface to notify the calling application when the
+ * dialog has completed.
+ */
+- (void)dialog:(NSString *)action
+ andParams:(NSMutableDictionary *)params
+ andDelegate:(id <FBDialogDelegate>)delegate {
+
+ [_fbDialog release];
+
+ NSString *dialogURL = [kDialogBaseURL stringByAppendingString:action];
+ [params setObject:@"touch" forKey:@"display"];
+ [params setObject:kSDKVersion forKey:@"sdk"];
+ [params setObject:kRedirectURL forKey:@"redirect_uri"];
+
+ if (action == kLogin) {
+ [params setObject:@"user_agent" forKey:@"type"];
+ _fbDialog = [[FBLoginDialog alloc] initWithURL:dialogURL loginParams:params delegate:self];
+ } else {
+ [params setObject:_appId forKey:@"app_id"];
+ if ([self isSessionValid]) {
+ [params setValue:[self.accessToken stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
+ forKey:@"access_token"];
+ }
+ _fbDialog = [[FBDialog alloc] initWithURL:dialogURL params:params delegate:delegate];
+ }
+
+ [_fbDialog show];
+}
+
+/**
+ * @return boolean - whether this object has an non-expired session token
+ */
+- (BOOL)isSessionValid {
+ return (self.accessToken != nil && self.expirationDate != nil
+ && NSOrderedDescending == [self.expirationDate compare:[NSDate date]]);
+
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//FBLoginDialogDelegate
+
+/**
+ * Set the authToken and expirationDate after login succeed
+ */
+- (void)fbDialogLogin:(NSString *)token expirationDate:(NSDate *)expirationDate {
+ self.accessToken = token;
+ self.expirationDate = expirationDate;
+ if ([self.sessionDelegate respondsToSelector:@selector(fbDidLogin)]) {
+ [_sessionDelegate fbDidLogin];
+ }
+
+}
+
+/**
+ * Did not login call the not login delegate
+ */
+- (void)fbDialogNotLogin:(BOOL)cancelled {
+ if ([self.sessionDelegate respondsToSelector:@selector(fbDidNotLogin:)]) {
+ [_sessionDelegate fbDidNotLogin:cancelled];
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+//FBRequestDelegate
+
+/**
+ * Handle the auth.ExpireSession api call failure
+ */
+- (void)request:(FBRequest*)request didFailWithError:(NSError*)error{
+ NSLog(@"Failed to expire the session");
+}
+
+@end
50 FBConnect/JSON/JSON.h
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2009 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ @mainpage A strict JSON parser and generator for Objective-C
+
+ JSON (JavaScript Object Notation) is a lightweight data-interchange
+ format. This framework provides two apis for parsing and generating
+ JSON. One standard object-based and a higher level api consisting of
+ categories added to existing Objective-C classes.
+
+ Learn more on the http://code.google.com/p/json-framework project site.
+
+ This framework does its best to be as strict as possible, both in what it
+ accepts and what it generates. For example, it does not support trailing commas
+ in arrays or objects. Nor does it support embedded comments, or
+ anything else not in the JSON specification. This is considered a feature.
+
+*/
+
+#import "SBJSON.h"
+#import "NSObject+SBJSON.h"
+#import "NSString+SBJSON.h"
+
68 FBConnect/JSON/NSObject+SBJSON.h
@@ -0,0 +1,68 @@
+/*
+ Copyright (C) 2009 Stig Brautaset. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the author nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+
+
+/**
+ @brief Adds JSON generation to Foundation classes
+
+ This is a category on NSObject that adds methods for returning JSON representations
+ of standard objects to the objects themselves. This means you can call the
+ -JSONRepresentation method on an NSArray object and it'll do what you want.
+ */
+@interface NSObject (NSObject_SBJSON)
+
+/**
+ @brief Returns a string containing the receiver encoded as a JSON fragment.
+
+ This method is added as a category on NSObject but is only actually
+ supported for the following objects:
+ @li NSDictionary
+ @li NSArray
+ @li NSString
+ @li NSNumber (also used for booleans)
+ @li NSNull
+
+ @deprecated Given we bill ourselves as a "strict" JSON library, this method should be removed.
+ */
+- (NSString *)JSONFragment;
+
+/**
+ @brief Returns a string containing the receiver encoded in JSON.
+