diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..0f877c7 --- /dev/null +++ b/README.markdown @@ -0,0 +1,137 @@ +# Facebook graph plugin + +This plugin provides access to the [Facebook Graph API] and makes easier the development of a single sign-on using the Facebook [Authentication proposal]. + +Source code: . Collaborations are welcome :-) + +## Configuration +Firstly, you should create a new application in the [Facebook developments page]. At the end of this process you will get an application id, an application secret code and an API key. Then, go to your Config.groovy file and add the application id and the application secret code: + + facebook.applicationSecret='' + facebook.applicationId='' + +Running the grails application locally, you may see this error returned from Facebook: + + API Error Code: 100 + API Error Description: Invalid parameter + Error Message: next is not owned by the application. + +In a nuthshell, Facebook cannot complete the call back the url specified in the settings of your FB application config. As a workaround, you can set the domain up in your hosts file. See this [thread] for more information. + +Now, your application is ready to interact with Facebook using their Graph API. + +##Single Sign-on with Facebook accounts +First, read the section "Single Sign-on with the JavaScript SDK" in . It is a good explanation about what we want to get. Read it, please. + +The plugin provides a tag that should be included in all pages (the main template is a good place): + + + +You can set an optional locale object in this tag, for instance: + + + +This tag adds the appropiate \ tag according to the request locale (or the locale set as attribute) and the call to FB.init function, needed to keep updated the Facebook cookie. The default options used in the FB.init call are {status:true, cookie: true, xfbml: true}. You can provide specific values for these attributes, for instance: + + + +The code inserted by fbg:resources by default is: + +
+ + + +If you prefer to use https instead of http in the facebook url to get the all.js file, set the configure property facebook.secure to true in your Config.groovy , with the applicationSecret and applicationId. + +Now you are ready to add your "Login with Facebook" button. First, add the fb namespace in your tag (again, the main layout is a good place): + + + +Then add the facebook login button where you want: + + + + + +Read this page to know more about permissions. The facebookLogin function is the Javascript function that will be called when the Facebook login ends successfully. An example of this function (that you should provide): + + + +With this function, if the facebook login has success, the application will redirect the browser to the action /auth/facebookLogin . To end the process, in this action I usually recover an object of the domain class that represents an user in my application (eg: User.groovy ). You could have a facebookUID attribute in this User class and you will have in this point a value in session.facebook.uid with the facebook UID of the authenticated user. Make a simple search in User and you will get your User object associated with the Facebook user authenticated. + +If you don't locate any User with this facebookUID it means that the Facebook authenticated user is new in your application, so you should create a new User object, associate the Facebook UID stored in session.facebook.uid and save it. Congrats, you have a new user from Facebook. + + The session.facebook map is maintains by a filter added in the plugin. All will be right if you include the tag in your pages + +##FacebookGraphService +The plugin provides this service to facilitate the access to the Facebook Graph API. Inject it in your artifacts declaring this attribute: + + def facebookGraphService + +If you have the in your pages and have a valid Facebook session, the filter added in the plugin will keep updated the map session.facebook , with information about your Facebook session. That is the unique requirement to use properly the FacebookGraphService . If you haven't got a valid session.facebook map the methods of the service will return null. + +###FacebookGraphService.getFacebookProfile() +This method returns the Facebook profile information about the user associated with the map in session.facebook . The Graph URL invoked finally is https://graph.facebook.com/me and the result is in JSON format. + +###FacebookGraphService.getFriends() +This method returns a list with all the friends of the user associated with the map in session.facebook . The Graph URL invoked finally is https://graph.facebook.com/me/friends and the result is in JSON format. + +###FacebookGraphService.publishWall(message) +This method publishes the message passed as parameter in the wall of the user associated with the map in session.facebook . The Graph URL where the post is made finally is https://graph.facebook.com/me/feed. The parameter should be a String. + +###FacebookGraphService.publishWall(map = [:]) +Since 0.7 version. This method publishes the map passed as parameter in the wall of the user associated with the map in session.facebook . The Graph URL where the post is made finally is https://graph.facebook.com/me/feed. The expected parameter is a map, so you can provide more than the message. For instance: + + facebookGraphService.publishWall(message:"The message", + link:"http://www.example.com/article.html", + name:"The name of the link") + +You can see the complete list of supported arguments in this [Facebook documentation page]. + +###FacebookGraphService.getProfilePhotoSrc(facebookUID) +This method generates the url of the public picture associated with the Facebook profile whose UID is equals to the passed parameter. It is a useful method to get the pictures of the Facebook friends of a user (first we call the getFriends method and then we call the getProfilePhotoSrc method for each friend). + +###FacebookGraphService.api(path, facebookData, params = [:], method = 'GET') +This is the basic method to interact with Facebook Graph API. + + path: The relative path that will be concated to https://graph.facebook.com URL to invoke the API method. See http://developers.facebook.com/docs/api for more information about valid paths. + facebookData: The map stored in session.facebook . + params: if they are needed by the method to invoke. + method: GET (default) or POST (to publish content) + +##Using in a Tag + +Create a tag in grails-app/taglib/ + + import grails.converters.JSON + class FacebookTagLib { + def facebookGraphService + + def fbInfo = { attrs -> if (session.facebook) { def myInfo = JSON.parse (facebookGraphService.getFacebookProfile().toString() ) + + out << "
id" << myInfo.id out << "
first_name:" << myInfo.first_name out << "
Last Name:" << myInfo.last_name out << "
Gender:" << myInfo.gender out << "
Timezone:" << myInfo.timezone out << "
Home Town:" << myInfo.hometown out << "
Link:" << myInfo.link out << "
Photo:" << "" + + } else { out << "Not logged in to Facebook" } } + + } + +and then in your view you can just use the following to display Facebook information about the currently logged in user + + + +[Facebook Graph API]: http://developers.facebook.com/docs/api +[Authentication proposal]: http://developers.facebook.com/docs/authentication +[Facebook developments page]: http://www.facebook.com/developers +[thread]: http://forum.developers.facebook.net/viewtopic.php?id=24390 +[Facebook documentation page]: http://developers.facebook.com/docs/reference/api/post \ No newline at end of file diff --git a/grails-app/services/com/daureos/facebook/FacebookGraphService.groovy b/grails-app/services/com/daureos/facebook/FacebookGraphService.groovy index 9236db0..8556dc3 100644 --- a/grails-app/services/com/daureos/facebook/FacebookGraphService.groovy +++ b/grails-app/services/com/daureos/facebook/FacebookGraphService.groovy @@ -221,7 +221,9 @@ class FacebookGraphService { if(facebookData) { // without a facebook session we'll return null result = oauthRequest(getUrl('graph', path), params, facebookData) - if(!result) throw new FacebookGraphException() + if(!result) { + throw new FacebookGraphException() + } else result = JSON.parse(result) log.debug("Result: ${result}") @@ -321,35 +323,39 @@ class FacebookGraphService { } encodedParams = encodedParams[0..-2] } - - try { - // Making the request - switch(params.method) { - case "GET": - if(encodedParams) urlAsString += '?' + encodedParams - url = new URL(urlAsString) - resp = url.text - break; - case "POST": - url = new URL(urlAsString) - connection = url.openConnection() - connection.setRequestMethod("POST") - connection.doOutput = true - - writer = new OutputStreamWriter(connection.outputStream) - writer.write(encodedParams) - writer.flush() - writer.close() - connection.connect() - - if (connection.responseCode == 200 || connection.responseCode == 201) - resp = connection.content.text - - break; - } - } catch(Exception e) { - // resp will be null, nothing to do... - log.error(e) + + // Making the request + switch(params.method) { + case "GET": + if(encodedParams) urlAsString += '?' + encodedParams + url = new URL(urlAsString) + resp = url.text + break; + case "POST": + url = new URL(urlAsString) + connection = url.openConnection() + connection.setRequestMethod("POST") + connection.doOutput = true + + writer = new OutputStreamWriter(connection.outputStream) + writer.write(encodedParams) + writer.flush() + writer.close() + connection.connect() + + if (connection.responseCode == 200 || connection.responseCode == 201) + resp = connection.content.text + else { + def response = connection.getHeaderFields() + def authenticateHeaderResponse = response.get("WWW-Authenticate") + def autneticateError + if (authenticateHeaderResponse != null){ + autneticateError = authenticateHeaderResponse[0].toString() + } + def errorMessage = connection.content.text + "\n" + autneticateError + throw new FacebookGraphException(errorMessage) + } + break; } return resp