Pixelastic

You can cut our wings but we will always remember what it was like to fly.

Posts tagged with "Facebook"

Facebook https mixed content warning on IE9

When browsing the web, you might have seen a "mixed content warning" popup show in your browser. They are most of the time poorly worded and their meaning is quite obscure.

What it means is that your are currently browsing a page with both http and https content. Typically, if your page is https but one of the css, js or image file it contains is requested through http, the warning will pop.

(It's interesting to note here that not all assets are treated equal. Loading a flash movie through http does not trigger the warning).

The IE special case

Now, what's special about IE is that it also trigger the mixed content warning when content is the other way around. If you're serving a page through http and load assets through https, it will consider it a mixed content too.

In a sense, that's logical, but it needlessly prompt the user with a dialog that block downloading of all https assets until confirmed.

The solution is to make sure that all your assets are loaded with the same protocol as the host page. An easy way to do so is to use the // relative protocol url. This will use http or https automatically based on the page protocol.

And adding Facebook to the mix

A few months ago, Facebook forced all apps to serve content through https. In the meantime, they suggested that all their users browse their website using https too.

Unfortunatly for us, some of our own users had bookmarks in their browsers and Facebook pages referencing our old http://apps.facebook.com/appname/ url. And following that link triggered the dreaded mixed content warning in IE.

When accessing this page, Facebook was loaded in classic http but our inner iframe was loaded through https. So far, so good.

The problem came from one of Facebook own javascript SDK included in our app. This script loaded other scripts based on what it needed.

Unfortunatly, it loaded the other scripts from an http (not https) server. The SDK has two distinct sets of urls, based on the current page protocol.

It was wrongly considering being in an http page, not an https one, and thus used the wrong set of urls. This confusing comes from the fact that it checks the top page protocol instead of checking the current page protocol.

After some googling, I found a solution that consisted in forcing the SDK to consider that we are in https mode by calling : FB._https = true before the call to FB.init()

Almost there

If correctly forced FB to use the correct set of urls, thus removing the mixed content warning. And I almost thought it would be that easy.

It was not.

This did not fixed the payment popup. All Facebook UI element loaded correctly (feeds, permissions, requests), but the payment popup.

I couldn't find a way to fix that, so I reverted to a more brutal approach.

Final and brutal solution

All my problems came from the fact that FB thought we were serving http while in fact we were serving https. So the solution, might be to force FB to serve https from start to end.

I wanted to detect if the page was loaded through http://apps.facebook.com/appname/ or https://apps.facebook.com/appname/.

Unfortunatly, due to cross domain restrictions in Javascript, we are not able to read, from inside and iframe, the parent frame properties. So I couldn't read top.location.protocol to easily check if I needed to redirect.

But, as I mentionned earlier, FB._https incorrectly report that we are not in https because it checks the top protocol. So I used this var, to know if the parent frame was in the correct protocol or not. Using this own FB bug to fix itself.

Now, for the redirect : even if I couldn't read the top.location, I could modify it. I just had to call top.location.url = 'https://apps.facebook.com/appname/' to redirect the whole page.

I hardcoded the app url because there was no way to get it from js, and I took care of keeping any GET parameter passed before the redirect, and I ended up with this :

if (!FB._https) {
var appUrl = 'https://apps.facebook.com/appname/';
var iframeUrl = location.protocol+'//'+location.host+location.pathname;
var redirectUrl = location.href.replace(iframeUrl, appUrl);
top.location.href = redirectUrl;
return;
}
FB.init(options);

Conclusion

I'm not really proud of this solution, as it is mostly a hack and will force a useless loading of the http version before loading the https one, but that's the best I've found. If any of you have a better solution, feel free to comment.

 

 

Getting list of Facebook friends from a test account

To get the list of friends of a specific user, you are supposed to issue a simple GET request to me/friends (or USER_ID/friends), passing the user accessToken in the request.

Facebook used to return an array named data where each value is an object representing a friend. Each of this objects had two keys : id and name.

Until yesterday, it worked well.

Today, it seems broken for test users. The name key is no longer returned in the call, you only get id.

I'm not sure it's intentionnal or not. At least I haven't seen any notice about this change. But I'm not sure it's a bug either, as Facebook documentation is crappy, we never really know if the output we got is the expected one or if it's a bug.

I'm not even sure we were supposed to get the name in the first place, actually.

What is definitely a bug is that the behavior is different for test users that for classic users.

Fix the floating issue with json_decode in PHP 5.3

When dealing with online API that are handling a lot of items (like Twitter or Facebook), you'd better be aware of a PHP limitation with json_decode.

json_decode is supposed to take a JSON string and return a PHP array or a PHP object from it.

Unfortunatly if one of the key is an int and is bigger than the max int value, it will be cast as float instead. And you'll lose precision in the process.

In my case it resulted in a complete unability to find a user in the database as the id didn't match anything. This was quite hard to find as I couldn't reproduce it on my local machine.

Know your system

My local system was a 64bits machine while the production servers were 32bits. And of course, max int precision is far bigger on 64bits machines so the error didn't pop in my tests.

If you're running PHP 5.4, the fix is easy. Just add the JSON_BIGINT_AS_STRING bitmask as 4th option like this

$decoded = json_decode($encoded, true, null, JSON_BIGINT_AS_STRING);

If you're running a 32bits machine with PHP 5.3 like me, it's a little more tricky.

The regexp

My solution is to parse the original JSON string and add quotes around ints so json_decode will keep them as string.

My first attempt was naive

preg_replace('/":([0-9]+),/', '":$1,', $encoded)

This will find any int between ": (marking the end of a key) and , (marking the start of the next key), and replace it with the same string but enclosed in quotes.

I soon found out that this did not cover all the cases, especially if the int key was the last of the JSON, it won't be followed by a , but by a } instead.

So, I adapted it a little bit :

preg_replace('/":([0-9]+)(,|})/', '":$1$2', $encoded)

Ok, it worked better. Any int key in the JSON, anywhere will be enclosed in quotes.

It was a little overkill so I decided to limit it to keys of at least 10 digits

preg_replace('/":([0-9]{10,})(,|})/', '":$1$2', $encoded)

Better. But still not perfect.

As I soon discovered, sometimes Facebook returned JSON containing JSON itself (in Request data or Credit order_info for example).

The previous approach would add quotes around ints in JSON escaped string and would thus corrupt the whole key.

This time, I added a final touch. I only added quotes around int that were not in an escaped JSON string themselves, by checking that the closing quote of the key wasn't escaped.

preg_replace('/([^\\\])":([0-9]{10,})(,|})/', '$1":$2$3', $encoded)

Here it is, the final fix. I might have forgotten some corner cases, but at least it works for my current application.

Hope it helps !


Accessing a frame with Firebug console

If you want to access the Javascript console of an inner frame of a webpage, know that you can "browse" through the window as you could browse through a file system.

For example, if you want Firebug to use the first frameof the page as its current window object, just type the following code in Firebug console :

cd(window.frames[0])

This proved immensely useful when debugging a Facebook application.

Testing Facebook Credits in a local environment

Testing Facebook API has always been a pain for me. Their documentation is still crappy and examples are wrong or/and outdated. Things don't exactly work the same way when you're testing with "Test account" or real accounts.

One of the things that helped me much was to create a test application and set its canvas url to a local server. As the iframe call is done through the client, I can test local code without the need to upload stuff to an online server.

I could even test Requests and Streams through this method as it's the Javascript SDK that does all the AJAX calls. The only tiny issue was with Stream images as Facebook requests them server-side before displaying them, I had to put some placeholder images online.

But today, with the testing of the new Facebook Credits API, new horizon of pains arised. Facebook will make no less than three calls to one of my server callback url, but does not make them client-side.

I still don't want to upload my code to a debug server just to test this feature, so I decided to put in place a little IP/port forwarding. Thanks to tips from my colleague Léo, this was done in a matter of minutes.

Setting up the DNS/port forwarding

First, we'll need a url that Facebook will call. I want this url to point to my local server. So all I had to do was to create a simple DNS redirect at dyndns.com that point to my local IP.

Let it be http://customname.dyndns-office.com/

Paste this url in the "callback url" field of your Facebook Credits config page.

Then, I'll assign a fixed internal IP to my computer so that it won't change on each reboot. My router can do that just fine, by assigning a fixed internal IP based on the MAC adress.

Let it be 192.168.0.51

Now, we'll redirect every call on the router through port 80 (http) to that url. My router admin panel can also do that just fine, in its DHCP configuration.

Finally, we'll have to update the server virtualhost config to point all incoming requests matching customname.dyndns-office.com to the server files.

A side note

There is one last little gotcha to be aware of.

It does not seem to be possible to access one of your network computer from an external IP (as we just configured) FROM one of your network computer.

In other words, if you would like to test your config, do not type http://customname.dyndns-office.com/ in your browser on one of the computers sharing the same network as 192.168.0.51.

Instead, use a free wifi connection, a ssh tunnel or curl from an external server you own.

In my case, locally testing http://customname.dyndns-office.com/ always brought me to my router admin panel and did not forward the port correctly. Doing a curl http://customname.dyndns-office.com/ from one of my online servers correctly returned what I wanted.

Back to Facebook

Back to our Facebook example, you still won't be able to see any outputs from the calls Facebook is making to your app. Your best bet is to have a logging system that will write on disk or in the DB so your can track the calls.

Also note that you have to load the page in the iframe canvas, even for your tests. You can't simply load an html page and call FB.ui({method:"pay"}), this will result in error 1151. Always load in the whole FB page.

Ghost posting on FB.ui streams

We had a bit of an issue when launching our app a few weeks ago. Everything was working fine on our test apps, but when live, all the stream messages we posted (we call them "Sharings") had a random text appended.

Most of the time it was a generic Facebook text, but sometimes it was a creepier SQL request just displayed plain on the user feed.

The Facebook text was (for the sake of search engine goodness):

Facebook is a social utility that connects people with friends and others who work, study and live around them. People use Facebook to keep up with friends, upload an unlimited number of photos, post links and videos, and learn more about the people they meet.

As the issue only occurs in production mode and never on any of our test environments, this was pretty difficult to debug.

Here was the code used to post the Sharing :

FB.ui({
[...],
'title' : 'Title of the Sharing',
'caption' : 'Text of the Sharing'
});

As I later found out, the caption key is not supposed to hold the Sharing text. The description key should be used for that. I'm not exactly sure was caption was for, but it seems that if you let the description key empty, then Facebook fills it automatically with a placeholder text.

The solution simply was to put the text in the description text, and leaving the caption key empty :

FB.ui({
'title' : 'Title of the Sharing',
'caption' : '',
'description' : 'Text of the Sharing'
});

As this behaviour is counter-intuitive, undocumented and random, I think posting it here could help other lost souls like me.

Missing bits of the official Facebook documentation

As a complement to my post about FB.ui undocumented hard limit, here are some other parts that should be in the documentation.

Custom html tag

You have to add xmlns:fb="http://www.facebook.com/2008/fbml" xmlns:og="http://opengraphprotocol.org/schema/" to your main <html> element.

If you don't, the <fb:like> (and possibly other <fb:*> elements) won't work in IE. I guess those unorthodox tags won't be interpreted by IE until you define the correct xmlns.

Adding a #fb-root

You also have to add a <div id="fb-root"></div> in your html code. I guess the Javascript API use it for some stuff, but I don't really know why.

The Javascript SDK logs an error message asking for this missing element if you don't have it.

Hard limits using FB.ui to post Requests

I hate Facebook documentation. All pages seems out of date, displaying wrong function signature, obsolete parameters, documentation link pointing to 404 pages, etc, etc.

I've lost some hours debugging those calls, finding whatever hard limit Facebook forced on some of the arguments.

Let us write a very basic example code :

FB.ui({
'method' : 'apprequests',
'display': 'iframe',
'message' : "Hey, this Request is awesome, just accept it, ok ?",
'title' : "Awesome request incoming",
'filters' : [
{ "name" : "Some friends", "user_ids" : [ "97841578", "548673131", "[...]"]  },
{ "name" : "Some other friends", "user_ids" : [ "97841578", "56867134", "[...]"]  },
]
});

This should open a Request popup with the custom title and message, as well as provide a list of friends that you can filter based on two criterias : "Some friends" and "Some other friends".

What will go wrong

First, you have to know that both the message and title have a character limit. If you go over it, the popup will simply display something like "An error occurred, blah blah blah".

After some fiddling, I discovered that the limit is 50 chars for the title and 255 for the message.

Also, there is no limit (as far as I know) to the limit of custom filters you can set. See update below. But there is one to the max number of users you can define in a filter. And that number is 1000.

This means that if my user_ids list for any of my custom filters contains more than 1000 users, the popup will fail. However, you can have as many filters with 999 users as you want.

Took me a little while to find, and I thought that this could be shared.

Update

I found a new hard limit : You can't set more than 5 filters at a time. If you add one more, the Request popup will fail.

New Update

Also, if you define a callback, don't forget to return true. Otherwise Webkit will refuse to close the FB popup and you'll have to click twice for it to really close itself.

Update (bis repetita)

This one was pretty hard to find but if your filter contains a facebook id of someone that is not a friend of the currently loggued user, the filter will display nothing.

In our app, we have a filter of "Neighbors" (as most social games do). But one of thoses neighbors removed the user from its friends, and we didn't update the neighbor list to reflect that, resulting in our "Neighbors" filter being empty.