iOS web/native app Facebook Login Popup - FAILS? - ios

I am trying to build a web app that will allow a user to login Facebook. Everything works in Firefox/Chrome/Safari (on the phone/tablet and on OSX).
When the App runs on the tablet (Native UIWebview and Web-app) it loads the first page perfectly.
When the user clicks the "connect with Facebook" button the app loads the Facebook logon page.
After the user logs in (again, in both a Native UIWebview and a web-app) the view turns white hanging on the URL: 'https://www.facebook.com/dialog/permissions.request?_path=permissions.request&app_id=[APP_ID]...' - this seems like it should not happen...
If I restart the app/web-app the user is logged in automatically, and redirected to the success page.
What I think is causing the problem
When you run the web page in the Firefox/Chrome/Safari browsers the Facebook login Dialog pops up as a popup or another tab (the latter on the native Safari browser).
I believe that this is a problem with this popup page and how the Javascript communicates with itself when a successful login takes place. Something with window.close where there is no root page to return to (as the web-app and UIWebview only have one instance of the webview)... maybe?
Failed work-around (UIWebview)
Since the app was hanging up on the previously mentioned URL I decided to add an if statement in shouldStartLoadWithRequest(...) to force the UIWebview to go to the success URL.
It loads the URL, but then before Facebook's Javascript SDK function FB.getLoginStatus function returns 'Connected' (It does return 'Connected' every time I've seen) The function FB.Event.subscribe('auth.logout' function() {...}); is fired.
I don't understand why it is logging the user out, then telling me that the user is connected (logged in) - in that order.
Any Ideas before I embark on trying to build this 100% native (and have to put up with apple's dev account and submitting the app)?
Login Script
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=APP_ID";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<script>
var seccond_page = false;
window.fbAsyncInit = function() {
FB.init({
appId : '[APP_ID]',
status : true,
cookie : true,
xfbml : true,
oauth : true
});
FB.Event.subscribe('auth.login', function(response) {
window.location.href = '<?= $success ?>';
});
FB.Event.subscribe('auth.logout', function(response) {
window.location.reload();
});
FB.login(function(response) {
alert(response.status);
if (response.status) {
if (response.status == 'connected') {
window.location.href = '<?= $success ?>';
}
}
}, {scope: 'email, user_likes, user_status, user_birthday, user_location, publish_checkins'});
$(document).on('click', '#fb_login_button', function() {
FB.login();
});
};
</script>
Success Page
<script>
var fb_user_id = '';
var fb_access_token = '';
var user_location = '';
window.fbAsyncInit = function() {
FB.init({
appId : '[APP_ID]',
status : true,
cookie : true,
xfbml : true,
oauth : true
});
FB.getLoginStatus(function(response) {
alert('Response - ' + response.status);
// the auth.logout is fired before the return of this in the failed fix
if (response.status === 'connected') {
if (response.authResponse) {
fb_user_id = response.authResponse.userID
fb_access_token = response.authResponse.accessToken;
}
}
});
FB.Event.subscribe('auth.logout', function(response) {
alert('logout - auth.logout');
// This event is fired before the above function in the failed fix
window.location.href = '<?= site_url('fb_login'); ?>';
});
FB.Event.subscribe('edge.create', function(response){
if (response == '<?= $like_url ?>') {
//action
}
});
};
</script>
All pages have the meta tag: <meta name="apple-mobile-web-app-capable" content="yes">

Facebook calls auth.logout just before auth.login for reasons not clear to me. You should inspect the response parameter before assuming that the user really has been logged out. Facebook docs state:
auth.logout - fired when the user logs out. The response object passed into the callback function looks like:
{
status: "", /* Current status of the session */
}
If you execute your logout handling only if response.status is really "", you may find that during login, it calls auth.login listeners immediately after calling the auth.logout listener. Your current auth.logout handling prevents you from noticing this, because the page reload stops JS and ajax executions.

Related

Ember-cli & Facebook-share buttons - how to?

Goal - Have share buttons for Facebook
I've had various problems, I've managed to solve most issues but never all at the same time.
ReferenceError: FB is not defined (error in the console)
"FB init function gives wrong version error" (error in the console)
My button does not render after transitioned to another route.
My button renders but there is no spacing around the button to adjacent stuff on the initial route
About that rendering issue (the - / hyphen is there to notice the spacing issue)
Rendering for the 1st time:
Rendering for the 2nd+ time:
What I've learned:
Facebook wants a <div id="fb-root"></div> as the first element inside your <body>
FB.XFBML.parse() can be called after didInsertElement to render a component after a transition
I made a JSBin boilerplate attempt, it's currently stuck at a undefined FB error.
http://emberjs.jsbin.com/fevoyuhiso/2/edit
Partial answer I'm also interested in:
Understanding how complex the solution at least must me to achieve a good result ("it must include an initializer, and a view/component!" or "you can solve this by just having ...")
Parts that may be of use
A post about "after FB.init()"
How to detect when facebook's FB.init is complete
An initializer
/* global FB */
export default {
name: 'facebook',
initialize: function() {
var fbAsyncInit = function() {
FB.init({
appId : 123,
xfbml : true,
version : 'v2.2'
});
};
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
window.fbAsyncInit = fbAsyncInit;
}
};
A component
/* global FB */
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'div',
classNames: 'fb-like',
attributeBindings: [
'data-href',
'data-layout',
'data-action',
'data-show-faces',
'data-share'
],
onDidInsertElement: function() {
Ember.run.schedule('afterRender', FB.XFBML.parse);
}.on('didInsertElement'),
init: function() {
Ember.run.schedule('afterRender', FB.XFBML.parse);
}
});
A script tag
<script type="text/javascript" src="//connect.facebook.net/en_US/sdk.js#xfbml=1&appId=123&version=v2.2"></script>
The root div facebook asks for
<div id="fb-root"></div>
Loading Facebook SDK
import ENV from "my-app/config/environment";
import setupOfflineMode from "my-app/utils/offline-mode";
export function initialize(container, application) {
// offline mode stubs `FB`
if (ENV.offlineMode) { return setupOfflineMode(); }
// Wait for Facebook to load before allowing the application
// to fully boot. This prevents `ReferenceError: FB is not defined`
application.deferReadiness();
var fbAsyncInit = function() {
initFacebook(window.FB);
application.advanceReadiness();
};
loadFacebookSDK();
window.fbAsyncInit = fbAsyncInit;
}
function loadFacebookSDK() {
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
}
function initFacebook(FB) {
FB.init({
appId: ENV.FB_APP_ID,
status: true,
cookie: true,
xfbml: true,
version: ENV.GRAPH_API_VERSION
});
}
export default {
name: 'facebook',
initialize: initialize
};
Share Links
I think this is all I needed to do; I hope I'm not forgetting something...
I didn't set up a component, so this is just a regular view, but it should work about the same.
<div class="fb-share-button" {{bind-attr data-href=link}} data-type="button"></div>
export default Ember.View.extend({
setupSocialNetworks: function() {
Ember.run.scheduleOnce('afterRender', this, function() {
FB.XFBML.parse();
});
}.on('didInsertElement')
});
UPDATE: Alternate solutions
I think which solution you use really depends on your needs. I'm focusing on a faster time-to-first-render, so I've changed my project to not deferReadiness for the Facebook SDK.
I've been playing with two solutions, again I think it totally depends on your needs.
Load the Facebook SDK in an initializer, but set a global promise for access.
This starts the loading on boot, but allows your application to continue booting without having to wait for Facebook. All calls to the Facebook API need to be accessed through the promise.
I'll share the details of this one if requested, but for now I'll just focus on the next solution:
Load the Facebook SDK only on demand in a service.
As before, all access to the Facebook API will need to go through a promise, but this time it is nicely encapsulated in a service and is only loaded on demand:
// app/services/facebook.js
import Ember from 'ember';
import ENV from "juniper/config/environment";
var computed = Ember.computed;
var RSVP = Ember.RSVP;
var _facebookSDKDeferrable = Ember.RSVP.defer();
var fbAsyncInit = function() {
_initFacebook(window.FB);
_facebookSDKDeferrable.resolve(window.FB);
};
window.fbAsyncInit = fbAsyncInit;
export default Ember.Service.extend({
// Resolves when the Facebook SDK is ready.
//
// Usage:
//
// facebook: Ember.inject.service(),
// foo: function() {
// this.get('facebook.SDK').then(function(FB) {
// // Facebook SDK is ready and FB is a reference to the SDK
// });
// }
SDK: computed.alias('FB'),
FB: computed(function() {
_loadFacebookSDK();
return _facebookSDKDeferrable.promise;
})
// I've also put promisified helpers for Facebook SDK
// methods here.
});
function _loadFacebookSDK() {
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
}
function _initFacebook(FB) {
FB.init({
appId: ENV.FB_APP_ID,
status: true,
cookie: true,
xfbml: false,
version: ENV.GRAPH_API_VERSION
});
}
Note also that I've set xfbml to false in FB.init. If your share links have been rendered before the SDK gets loaded, FB.init({ xfbml: true }) will "parse" them, and your FB.XFBML.parse() call will do it again. By setting xfbml to false, you ensure that FB.XFBML.parse() will only get called once.

Using Omniauth facebook and ajax

I'm trying to allow a user to sign in via facebook, and not be redirected off of the current page. I'm currently using Rails with omniauth-facebook and devise for authentication. I'm assuming the best way to do this is via ajax once I've received authentication from facebook via the Javascript api. However, I'm not sure what I need to pass to the callback url for omniauth to verify the authentication. Here's what I currently have (I'm trying to avoid using jquery for the time being)
:javascript
window.fbAsyncInit = function() {
// init the FB JS SDK
FB.init({
appId : 'app-id', // App ID from the app dashboard
channelUrl : '//localhost:3000/channel.html', // Channel file for x-domain comms
status : true, // Check Facebook Login status
cookie : true,
xfbml : true // Look for social plugins on the page
});
// Additional initialization code such as adding Event Listeners goes here
document.getElementById('facebook-login').onclick = function(event) {
FB.login(function(response) {
if (response.authResponse) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
console.log(xhr);
if (xhr.readyState == 4) {
console.log(xhr.responseText);
}
}
xhr.open('GET', 'http://localhost:3000/users/auth/facebook/callback', true);
xhr.send(null);
} else {
console.log("Something when horrible wrong");
}
}, {scope: ''});
}
};
// Load the SDK asynchronously
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
The thing I'm most unsure about is if I'm calling my own endpoint (users/auth/facebook/callback) correctly, or if I need to pass anything to it. Any help would be greatly appreciated.
For me, the problem was making a GET request instead of a POST request.
Changed to POST and the problem is gone.
I would have left it as a comment, as I'm not leaving further information to justify this solution, but i'm new at this (StackOverflow); I don't have enough points to be able to comment.
Anyway, Hope it helps!

Post data to controller with JQuery via login screen

I have a section in my ASP.NET MVC3 website where a user can click a button to add an entry to their 'Saved Items' section in their account. This is done via a JQuery Ajax request, which works well if they're logged in. If they're not logged in, I'd like them to be redirected to a login page, and then automatically have the entry added to their Saved Items section.
I have all the parts working seperately - i.e. when the button is clicked, if not logged in, the login box displays. The login popup also works successfully. The problem is trying to seamlessly do all things at once. Here is the code I have so far:
Click event for Save button - checks to see if user logged in along the way:
var loggedIn = false;
$(document).ready(function () {
$('a#saveSearch').live('click', function (event) {
$.get('#Url.Action("IsLoggedIn", "Account", null)', function (response) {
if (response == "True")
loggedIn = true;
else
loggedIn = false;
});
if (loggedIn){
SaveSearch();
}
else{
$('#dialog').dialog('open');
SaveSearch(); //don't think this is correct because it hits this line before login is complete
}
});
Function to save to database:
function SaveSearch(){
var url = '#Url.Action("SaveSearch", "User")';
$.ajax({
url: url,
type: 'POST',
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
json: "#Html.Raw(Session["MyFormString"].ToString())"
}),
success: function (data) {
$('a#saveSearch').attr('disabled', "disabled");
$('div#savedResponse').html('<p>Search saved to user account</p>');
},
error: function () {
}
});
}
});
JQuery UI dialog popup:
$(function () {
$('#dialog').dialog({
autoOpen: false,
width: 400,
resizable: false,
title: 'Login',
modal: true,
open: function(event, ui) {
$(this).load("#Url.Action("Logon", "Account", null)");
},
buttons: {
"Close": function () {
$(this).dialog("close");
}
}
});
I think there is something fundamental that is wrong with my code, because this way, the login popup appears for just a second and then disappears straight away. It looks like I need to get it to stop advancing through the code until the login has been completed.
Any advice or help to get this going would be appreciated.
I would imagine your issue might be related to:
$.get('#Url.Action("IsLoggedIn", "Account", null)', function (response) {
if (response == "True")
loggedIn = true;
else
loggedIn = false;
});
if (loggedIn){
SaveSearch();
}
else{
$('#dialog').dialog('open');
SaveSearch(); //don't think this is correct because it hits this line before login is complete
}
The $.get call is async, which means the latter code:
if (loggedIn){
Is being executed before the server has responded. You need to put that code within your response callback:
$.get('#Url.Action("IsLoggedIn", "Account", null)', function (response) {
if (response == "True")
loggedIn = true;
else
loggedIn = false;
if (loggedIn){
SaveSearch();
}
else{
$('#dialog').dialog('open');
SaveSearch(); //don't think this is correct because it hits this line before login is complete
}
});
Try and add a close callback function to your modal, then the code will only be done as soon as the modal is closed and all the login have been done sucessfully. See comments in your code
$(document).ready(function () {
$('a#saveSearch').live('click', function (event) {
$.get('#Url.Action("IsLoggedIn", "Account", null)', function (response) {
if (response == "True")
loggedIn = true;
else
loggedIn = false;
});
if (loggedIn){
SaveSearch();
}
else{
//in this dialog, add a close handler,then add the SaveSearch(); function in that handler
$('#dialog').dialog('open');
}
});

FB.login popup on mobile web doesn't auto close once logged in

I have a Facebook connect site and am having issues with the Facebook login part on mobile browsers (ie: iPhone Safari, etc). Whenever I click the login button, it opens a new tab in Safari which takes me to a URL like this: https://s-static.ak.fbcdn.net/connect/xd_proxy.php... followed by a bunch of extra hashes.
The problem is, this page doesn't auto close and go back to my app. I have to manually close it and then go back. The login process works fine and the other page has already refreshed and loaded, but I can't figure out why it's not auto closing the XD Proxy page.
I've followed all the tutorials on Facebook for setting up the javascript code. I'm using FB.login() to actually login. It works flawlessly on the desktop. I'm wondering if this is a bug in Facebook's javascript or not, but I can't seem to figure it out. Any ideas would be very helpful.
Here is the code I'm using, for reference:
<script>
window.fbAsyncInit = function() {
FB.init({
appId: "APP_ID",
status: true,
cookie: true,
oauth: true,
xfbml: true
});
};
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<button onclick="loginToFB();" type="button" class="fb-login-button">Login with Facebook</button>
<script type="text/javascript">
function loginToFB(next) {
FB.login(function(response) {
if (response.authResponse) {
window.location = '/facebook';
}
}, {scope: 'SCOPE_STRING'});
}
</script>
Thanks!
Try first calling the getLoginStatus() function and don't do FB.login() if the user is already logged in.

Can Facebook's Javascript SDK work together with the older Facebook API?

Can the current Facebook Javascript SDK work with older Facebook API library?
Right now there is code to load the current Facebook Javascript SDK by:
window.fbAsyncInit = function() {
FB.init({appId: '218565466928', status: true, cookie: true,
xfbml: true});
};
// [...] initialize it
And there is code to use the old Facebook API by
init_fb_connect('Connect', 'XFBML', :js => :jquery, :app_settings=> '{ifUserNotConnected: fb_user_not_connected}' ) do
which is the Facebooker Rubygem. Can they work together somehow? If I have both, then the newly added "Like" button won't work. If I remove the older Facebooker code, then the "Login with Facebook" and "Share" button won't work. Any ideas?
Update: the older code do things like:
<a class="facebook-connect-link " href="#"
onclick="; FB.Connect.requireSession(fb_after_connect, null, true); return false;"
rel="nofollow">Sign in with Facebook</a>
and
<a href="#" onclick="FB.Connect.streamPublish('', {'name': 'Some product name' ...
and
$('.login-button').each(function() {
FB.XFBML.Host.addElement(new FB.XFBML.LoginButton(this));
})
Converting the JavaScript API is relatively easy. I am not sure how much your server side will be affected though. Here are the basic methods that you would probably need:
//Check if user is logged in right now.
FB.getLoginStatus(function(response) {
if (response.session) {
// logged in and connected user, someone you know
} else {
// no user session available, someone you dont know
}
});
//Callback fired when user logs out/logs in.
FB.Event.subscribe('auth.sessionChange', function(response) {
// do something with response.session
});
//To force login (on login btn click).
FB.login(function(response) {
if (response.session) {
// user successfully logged in
fb_after_connect();
} else {
// user cancelled login
}
});
//Post to feed.
FB.api('/me/feed', 'post', { body: "message" }, function(response) {
if (!response || response.error) {
alert('Error occured');
} else {
alert('Post ID: ' + response);
}
});
If you don't want to convert to the new API then you can embed like button as iframe. Sooner or later you would have to convert your project anyway so might as well do it now.

Resources