Thursday, June 28, 2012

Facebook Chat API


You can integrate Facebook Chat into your Web-based, desktop, or mobile instant messaging products. Your instant messaging client connects to Facebook Chat via the Jabber/XMPP service. This document describes the features and limitations of Facebook Chat's XMPP protocol for the developer who intends to implement a Facebook Chat client.
Before reading this document, you should be familiar with the terms and concepts involved in XMPP chat clients and servers.
Please do not use this API to send spammy messages to users. Facebook takes user experience and spam extremely seriously and if users report your app as using the Chat API to spam them, we will disable your app.



Features and Limitations

Facebook Chat currently supports the following features:
  • Facebook Platform authentication using the X-FACEBOOK-PLATFORM SASL authentication mechanism
  • Username/password authentication using the DIGEST-MD5 authentication mechanism
  • Sending and receiving plain-text messages (not HTML messages)
  • Sending and receiving typing notifications using the XEP-0085 protocol extension (not the XEP-0022 extension)
  • Setting the user idle using a show element in presence stanzas (there will be a delay before the user appears idle)
  • Receiving vCards using the XEP-0054 extension
  • Retrieving friends' photos (either with vCard or XMPP presence)

Limitations

Facebook Chat should be compatible with every XMPP client, but is not a full XMPP server. It should be thought of as a proxy into the world of Facebook Chat on www.facebook.com. As a result, it has several behaviors that differ slightly from what you would expect from a traditional XMPP service:
  • Your client cannot send or receive HTML messages
  • Because roster items and presence subscriptions are based on the user's Facebook friends, they cannot be created or deleted using the standard XMPP mechanisms.
  • Facebook Chat is terse when sending updates for new friends, because the negotiation happens outside of XMPP. Future versions of Facebook Chat may be more conformant.
  • The user's own Jabber ID (JID) is different from the Jabber ID that their contacts will see because the translation is done internally.
  • Arbitrary IQ stanzas cannot be passed between clients.
  • Presence probes do not currently work.
  • Non-SASL authentication with the jabber:iq:auth namespace as described in XEP-0078 is not currently supported.
  • The XML parser does not yet fully handle XML namespaces. Please stick to the same style as the examples in XMPP RFCs 3920 and 3921 when using XML namespaces.

Configuring Chat Authentication

You can authenticate your chat client users with one of two authentication mechanisms: X-FACEBOOK-PLATFORM (Facebook Platform) and DIGEST-MD5 (username/password). Facebook recommends you use the X-FACEBOOK-PLATFORM mechanism to connect to Facebook Chat whenever possible, because it provides a better user experience using simple Facebook Platform authentication.

Authenticating with Facebook Platform

We support a custom SASL mechanism called X-FACEBOOK-PLATFORM that allows clients to connect to chat using Facebook authentication. This mechanism is preferred for any application that is oriented toward social media in general or Facebook in particular, especially applications that are already integrated with Facebook Platform.
In order to connect using this mechanism, the user must first log in to your application and grant the xmpp_login extended permission. Follow the client side flow to get a valid access_token for the user with the xmpp_login extended permission.
Your application may now log in to Facebook Chat via Jabber using the X-FACEBOOK-PLATFORM mechanism. The user's Jabber ID will be assigned during the resource binding step of XMPP. Please keep in mind that while all of the messages defined by the X-FACEBOOK-PLATFORM mechanism are UTF-8 strings, XMPP specifies that they should be Base64-encoded before being sent over the wire.
The mechanism starts with a server challenge, in the form of a common HTTP query string: an ampersand-separated sequence of equals-sign-delimited key/value pairs. The keys and values are UTF-8-encoded and URL-encoded. The query string contains two items: method and nonce.
The client's reply should be a similarly-encoded query string prepared as if it were going to call a method against the Facebook API. The call should contain the following parameters:
  • string method: Should be the same as the method specified by the server.
  • string api_key: The application key associated with the calling application.
  • string access_token: The access_token obtained in the above step.
  • float call_id: The request's sequence number.
  • string v: This must be set to 1.0 to use this version of the API.
  • string format: Optional - Ignored.
  • string cnonce: Optional - Client-selected nonce. Ignored.
  • string nonce: Should be the same as the nonce specified by the server.
The server will then respond with a success or failure message. Note that this needs to be over TLS or you'll get an error.

Authenticating with Username/Password

The DIGEST-MD5 SASL mechanism is available to support traditional XMPP or multi-protocol IM clients that are not customized for (or even aware of) Facebook. This mechanism requires prompting the user for his or her password; therefore, it should only be used when necessary. In particular, it MUST NOT be used for any client that:
  • Proxies the XMPP connection (the connection must be directly from the user's computer to Facebook).
  • Reports messages or any other information about user activity to a third party (including the client developer).
  • Integrates with Facebook, or has a Facebook Application ID.
If your application does any of the above, you must use Facebook Platform authentication instead.
The user's Jabber ID is simply his or her Facebook user name with @chat.facebook.com appended. A user must have a Facebook username to use DIGEST-MD5. After the user gets his or her username, he or she must log out of and into Facebook once for us to store the special hash of the password.
Clients should retrieve their user's vCard from the server according to XEP-0054 in order to be able to display a more natural name to the user. For example:
<iq id='1' type='get'><vCard xmlns='vcard-temp'/></iq>

Best Practices

In order to provide the best user experience, we recommend your chat integration do the following:
  • Your Facebook Chat integration should only be used for applications facilitating real time conversation or interaction between users. Links or advertisements should not be sent via chat, unless the sending user types in this message.
  • Your Facebook Chat integration should only be used for sessions that are expected to be long-lived. Clients should not rapidly churn on and off.
  • vCards retrieved through Facebook Chat will contain profile pictures if available. Clients should cache these pictures using the content hash, not the user ID, as the key. vCards should only be fetched if the client does not already have that user's picture cached.
  • Clients should not automatically reconnect if they receive a stream-error of type conflict.
  • Clients should be able to handle a single contact with multiple group elements.
  • Incoming messages from the JIDs chat.facebook.com or facebook.com should be displayed as administrative messages.

Sample code

Below is a sample code that shows you how to authenticate a user and connect with XMPP.
<?php
// Copyright 2004-present Facebook. All Rights Reserved.

$STREAM_XML = '<stream:stream '.
  'xmlns:stream="http://etherx.jabber.org/streams" '.
  'version="1.0" xmlns="jabber:client" to="chat.facebook.com" '.
  'xml:lang="en" xmlns:xml="http://www.w3.org/XML/1998/namespace">';

$AUTH_XML = '<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" '.
  'mechanism="X-FACEBOOK-PLATFORM"></auth>';

$CLOSE_XML = '</stream:stream>';

$RESOURCE_XML = '<iq type="set" id="3">'.
  '<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">'.
  '<resource>fb_xmpp_script</resource></bind></iq>';

$SESSION_XML = '<iq type="set" id="4" to="chat.facebook.com">'.
  '<session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>';

$START_TLS = '<starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"/>';


function open_connection($server) {
  print "[INFO] Opening connection... ";

  $fp = fsockopen($server, 5222, $errno, $errstr);
  if (!$fp) {
    print "$errstr ($errno)<br>";
  } else {
    print "connnection open<br>";
  }

  return $fp;
}

function send_xml($fp, $xml) {
  fwrite($fp, $xml);
}

function recv_xml($fp,  $size=4096) {
  $xml = fread($fp, $size);
  if ($xml === "") {
     return null;
  }

  // parses xml
  $xml_parser = xml_parser_create();
  xml_parse_into_struct($xml_parser, $xml, $val, $index);
  xml_parser_free($xml_parser);

  return array($val, $index);
}

function find_xmpp($fp,  $tag, $value=null, &$ret=null) {
  static $val = null, $index = null;

  do {
    if ($val === null && $index === null) {
      list($val, $index) = recv_xml($fp);
      if ($val === null || $index === null) {
        return false;
      }
    }

    foreach ($index as $tag_key => $tag_array) {
      if ($tag_key === $tag) {
        if ($value === null) {
          if (isset($val[$tag_array[0]]['value'])) {
            $ret = $val[$tag_array[0]]['value'];
          }
          return true;
        }
        foreach ($tag_array as $i => $pos) {
          if ($val[$pos]['tag'] === $tag && isset($val[$pos]['value']) &&
            $val[$pos]['value'] === $value) {
              $ret = $val[$pos]['value'];
              return true;
          }
        }
      }
    }
    $val = $index = null;
  } while (!feof($fp));

  return false;
}


function xmpp_connect($options, $access_token) {
  global $STREAM_XML, $AUTH_XML, $RESOURCE_XML, $SESSION_XML, $CLOSE_XML, $START_TLS;

  $fp = open_connection($options['server']);
  if (!$fp) {
    return false;
  }
 
  // initiates auth process (using X-FACEBOOK_PLATFORM)
  send_xml($fp,  $STREAM_XML);
  if (!find_xmpp($fp, 'STREAM:STREAM')) {
    return false;
  }
  if (!find_xmpp($fp,  'MECHANISM', 'X-FACEBOOK-PLATFORM')) {
    return false;
  }

  // starting tls - MANDATORY TO USE OAUTH TOKEN!!!!
  send_xml($fp,  $START_TLS);
  if (!find_xmpp($fp, 'PROCEED', null, $proceed)) {
    return false;
  }
  stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);

  send_xml($fp, $STREAM_XML);
  if (!find_xmpp($fp, 'STREAM:STREAM')) {
    return false;
  }
  if (!find_xmpp($fp, 'MECHANISM', 'X-FACEBOOK-PLATFORM')) {
    return false;
  }

  // gets challenge from server and decode it
  send_xml($fp, $AUTH_XML);
  if (!find_xmpp($fp,  'CHALLENGE', null, $challenge)) {
    return false;
  }
  $challenge = base64_decode($challenge);
  $challenge = urldecode($challenge);
  parse_str($challenge, $challenge_array);

  // creates the response array
  $resp_array = array(
    'method' => $challenge_array['method'],
    'nonce' => $challenge_array['nonce'],
    'access_token' => $access_token,
    'api_key' => $options['app_id'],
    'call_id' => 0,
    'v' => '1.0',
  );
  // creates signature
  $response = http_build_query($resp_array);

  // sends the response and waits for success
  $xml = '<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">'.
    base64_encode($response).'</response>';
  send_xml($fp, $xml);
  if (!find_xmpp($fp, 'SUCCESS')) {
    return false;
  }
  
  // finishes auth process
  send_xml($fp, $STREAM_XML);
  if (!find_xmpp($fp,'STREAM:STREAM')) {
    return false;
  }
  if (!find_xmpp($fp, 'STREAM:FEATURES')) {
    return false;
  }
 send_xml($fp, $RESOURCE_XML);
  if (!find_xmpp($fp, 'JID')) {
    return false;
  }
  send_xml($fp, $SESSION_XML);
  if (!find_xmpp($fp, 'SESSION')) {
    return false;
  }

  // we made it!
  send_xml($fp, $CLOSE_XML);
  print ("Authentication complete<br>");
  fclose($fp);

  return true;
}



//Gets access_token with xmpp_login permission
function get_access_token($app_id, $app_secret, $my_url){ 
    
  $code = $_REQUEST["code"];

  if(empty($code)) {
    $dialog_url = "https://www.facebook.com/dialog/oauth?scope=xmpp_login".
     "&client_id=" . $app_id . "&redirect_uri=" . urlencode($my_url) ;
    echo("<script>top.location.href='" . $dialog_url . "'</script>");
  }
   $token_url = "https://graph.facebook.com/oauth/access_token?client_id="
    . $app_id . "&redirect_uri=" . urlencode($my_url) 
    . "&client_secret=" . $app_secret 
    . "&code=" . $code;
   $access_token = file_get_contents($token_url);
    parse_str($access_token, $output);
    
    return($output['access_token']);
}

function _main() {
  print "Test platform connect for XMPP<br>";
  $app_id='YOUR_APP_ID';
  $app_secret='YOUR-APP_SECRET';
  $my_url = "YOUR_APP_URL";
  $uid = 'USER_ID';
  $access_token = get_access_token($app_id,$app_secret,$my_url);
  print "access_token: ".$access_token."<br>";

  $options = array(
    'uid' => $uid,
    'app_id' => $app_id,
    'server' => 'chat.facebook.com',
   );

  // prints options used
  print "server: ".$options['server']."<br>";
  print "uid: ".$options['uid']."<br>";
  print "app id: ".$options['app_id']."<br>";

  if (xmpp_connect($options, $access_token)) {
    print "Done<br>";
  } else {
    print "An error ocurred<br>";
  }

}

_main();


No comments:

Post a Comment