December 12, 2007
XMPP in your browser: Flex 2 with XIFF
An entry about using the XIFF API to get your Flex application talking XMPP. If you don’t know what I’m talking about no need to read on really.
Some definitions
- XMPP is an open protocol for instant messaging, that runs the Jabber network and Google talk.
- Flex 2 is a programmer-friendly way of developing Flash applications, using ActionScript 3, which looks a lot like Java.
- XIFF is an ActionScript 3 API for XMPP.
If you put those three items together you get, yes, a paradigm shift in web based application development. But let’s not get fancy just yet (a later blog post might wax lyrical about the future of web apps), for now here are some recipes for doing just about anything with XMPP, ActionScript 3, and XIFF.
Permission to connect
The Flash client in the browser is by default only allowed to access the exact domain it came from, so if your XMPP server is on chat.myserver.com
(or even just myserver.com
), and your Flash was served from www.myserver.com
, you need a crossdomain.xml file.
< ?xml version='1.0'>
<!DOCTYPE cross-domain-policy SYSTEM 'http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd'>
<cross-domain-policy>
<allow-access-from domain='*' />
</cross-domain-policy>
[Remove the space between the first < and the ?]
Save this in the root of chat.myserver.com
, and make sure an HTTP server is there ready to serve it. If you are not using the standard XMPP port 5222, but instead are using a port below 1024 (possibly to get through a firewall), add to-ports="*"
to the allow-access-from
line.
Now start your Flex application with:
import flash.system.Security;
Security.loadPolicyFile("http://"+ server +"/crossdomain.xml");
The Openfire server provides a built-in crossdomain.xml for you on an xml socket on port 5229, so on that server you can skip creating the file , you don’t need to run an HTTP server (although the user has to get your Flash from somewhere), and use:
Security.loadPolicyFile("xmlsocket://"+ server +":5229");
Only port 80 is permitted for HTTP serving of policy files – any other port must use an xmlsocket. See Actionscript docs for Security.loadPolicyFile
Create the connection
import org.jivesoftware.xiff.core.XMPPSocketConnection;
connection = new XMPPSocketConnection();
Use XMPPSocketConnection instead of just XMPPConnection, as the later doesn’t work correctly with ejabberd.
Get the roster ready
import org.jivesoftware.xiff.im.Roster;
roster = new Roster(connection);
roster.addEventListener(RosterEvent.SUBSCRIPTION_DENIAL, rosterHandler);
roster.addEventListener(RosterEvent.SUBSCRIPTION_REQUEST, rosterHandler);
roster.addEventListener(RosterEvent.SUBSCRIPTION_REVOCATION, rosterHandler);
roster.addEventListener(RosterEvent.USER_AVAILABLE, rosterHandler);
roster.addEventListener(RosterEvent.USER_UNAVAILABLE, rosterHandler);
private function rosterHandler(event:RosterEvent):void {
trace("rosterHandler");
switch (event.type){
case RosterEvent.SUBSCRIPTION_REQUEST:
// Fill this bit in, obviously
break;
case RosterEvent.USER_UNAVAILABLE :
trace (event.jid + " is Unavailable (RosterEvent)");
break;
case RosterEvent.USER_AVAILABLE :
trace (event.jid + " is Available (RosterEvent)");
break;
case RosterEvent.SUBSCRIPTION_DENIAL :
trace (event.jid + " denied your request (RosterEvent)");
break;
case RosterEvent.SUBSCRIPTION_REVOCATION :
// this fires at unexpected times so ignore it.
// trace (event.jid + " revoked your presence (RosterEvent)");
break;
default :
// do nothing... not recognized
}
}
Login
connection.username = "webuser";
connection.password = "secret";
connection.server = "chat.myserver.com";
connection.resource = "Web";
connection.connect("standard");
At this stage if any of ‘webuser’s contacts are online the USER_AVAILABLE event will fire once for each, and connectionManager.isLoggedIn()
should return true.
Register a new account
It seems you need to fail a login before you can register a new user, and after creating a new user you need to disconnect and reconnect as that user (assuming you want to use the account you just created):
connection.addEventListener(XIFFErrorEvent.XIFF_ERROR, registerNewUser);
connection.addEventListener(
RegistrationSuccessEvent.REGISTRATION_SUCCESS, reconnect);
then try and login with a non existent username and password (see Login section above). The login failure will call your method:
private function registerNewUser(event:XIFFErrorEvent):void {
trace("registerNewUser");
if ( event.errorCode != 401 || event.errorType != "auth" ) {
// Only act on failed login, ignore other errors
return;
}
username = "newUser";
password = "newUserPassword";
var registrationFields:Object = new Object();
registrationFields.username = username;
registrationFields.password = password;
connection.sendRegistrationFields(registrationFields, null);
connection.removeEventListener(XIFFErrorEvent.XIFF_ERROR, registerNewUser);
}
Your server needs to allow registrations with only the username and password, and to allow registrations from anyone. This is usually the server default, although they might insist on an e-mail. Successful registration will call the method you registered earlier:
private function reconnect(event:RegistrationSuccessEvent):void {
trace("reconnect");
connection.disconnect();
initRoster();
login();
}
The initRoster
and login
will do the obvious – see the sections above.
Subscribe to the presence of another user
var userJID:String = "bob@yourserver.com";
roster.addContact(agentJID, "Bobby", "People", true);
This will ask ‘bob@yourserver.com’ if they allow your user to subscribe to their presence. When they reply, which may be some time if they are not online, it will fire one of RosterEvent.SUBSCRIPTION_DENIAL
if they deny you, RosterEvent.SUBSCRIPTION_REQUEST
if they choose to subscribe to your presence in turn, and, if they authorized you and are online, RosterEvent.USER_AVAILABLE
.
Multi-user chat
To chat with people you create a room, join that room, then invite them to join that room. room = new Room(); room.setRoomJID( roomJID ); // See explanation after the code room.setConnection( connection );
room.addEventListener(RoomEvent.USER_JOIN, roomHandler);
room.addEventListener(RoomEvent.USER_DEPARTURE, roomHandler);
room.addEventListener(RoomEvent.GROUP_MESSAGE, roomHandler);
room.addEventListener(RoomEvent.ROOM_JOIN, inviteToRoom); // Invite the guests when we join the room
// We need the room in our roster to handle presence messages of people in the room
if ( roster.getContactInformation(roomJID) == null ) {
roster.addContact(roomJID, "Something", "People", false);
}
room.join();
Multi-User chat is an extension to Jabber, so usually uses a separate server. In the case of jabberd this is a physically separate server called ‘mu-conference’. For Openfire and ejabberd this is built in. Hence the roomJID is usually at the server prefixed with ‘conference’: ‘myroomname@conference.yourserver.com’
When you join the room your method will be called: private function inviteToRoom(event:RoomEvent):void { trace(“ConnectionManager.inviteToRoom”); room.invite( “bob@yourserver.com”, “Come and chat” ); }
The roomHandler
method should be straighforward – copy the earlier rosterHandler
and change the events.
Auto-reconnect
If your Flash is going to be sitting in someone’s open browser for any length of time, the little pixies that run down the pipes that make up the Internet might accidentally disconnect you – remember that your Flex / Flash app is holding an open socket to the XMPP server the whole time the page is open. Hence you need to auto-reconnect:
connection.addEventListener(XIFFErrorEvent.XIFF_ERROR, reconnectOnError);
private function reconnectOnError(event:XIFFErrorEvent):void {
trace("reconnectOnError");
if ( event.errorCode == 503 ) { // Service unavailable, means we got disconnected
this.connection = null;
connect(); // Creates a new connection, a new roster, and logs in
if ( room != null ) { // If in a chat rejoin that room
rejoinRoom( room.roomName );
}
}
}
Choosing an XMPP Server
You’re obviously going to need a server to do this. If you have lots of memory, prefer:
Openfire – Built by the company behind XIFF, this is the easiest to install and most fully featured XMPP server out there right now. It’s in Java so it’s not appropriate if your server memory is limited, such as on a shared host or UML server. It has a great web admin interface (port 9090 by default). I use this for development.
Otherwise choose between these two:
Ejabberd – Written in Erlang, this is the one to choose if your site has an insanely large amount of visitors. Also has a pretty decent web admin interface, on
yourserver.com:5280/admin/
. To login you need to create an account via your Jabber client, authorize that account as admin in/etc/ejabberd/ejabberd.cfg
, and login using your full JID, such as ‘admin@yourserver.com’, and your password.- Jabberd 1.4 with mu-conference – Standard Unix C/C++ server, and I think the original Jabber server. Makes your sysadmin happy because they are good o’ regular Unix daemons. This is the one I use live because it takes 9M of memory, with only 2M resident. No fancy admin interfaces here – there’s some shell scripts floating around, or hack the database directly.
Debugging
Get Wireshark so you can see what your Flex app and the XMPP server are saying to each other.
Create a mm.cfg
file in your home directory with these lines:
ErrorReportingEnable=1
TraceOutputFileEnable=1
MaxWarnings=1
SecurityDialogReportingEnable=true
this allows you to use the trace
method in ActionScript to log to ~/.macromedia/Flash_Player/Logs/flashlog.txt
.
Editing tips
In Eclipse associate .mxml
with the XML editor, and .as
with the Javascript editor, and you’ll get syntax highlighting and in ActionScript the Outline window will work.
References
- XIFF API docs – unofficial and invaluable
- Nick Velloff’s blog post and AS3 XMPP client – full working example with source code
le said,
March 16, 2012 at 04:49
Thank you very much for a very detailed article!
josé said,
July 1, 2010 at 10:26
I connect to server chat.facebook.com but don´t login i try connection = new XMPPSocketConnection(); connection.username = “?????”; connection.password = “?????”; connection.server = “chat.facebook.com”; connection.port = 5222;
connection.addEventListener(ConnectionSuccessEvent.CONNECT_SUCCESS, connect); connection.addEventListener(LoginEvent.LOGIN, onLogin);
connection.connect(“standard”);
in the handler connect are value true but don´t complet the login… anyone have ideas…thanks zeze_arcos@hotmail.com
tobe said,
April 7, 2010 at 19:23
AWESOME! Realise I’m a bit late in the day picking up this forum post, but still wanted say big thanks for the pointers. Really helped me out
Marco Simão said,
November 20, 2008 at 18:39
Hey dude, i came up with this problem and got stucked with no straigth answer on what could preventing my flex app to do this kind of access. So THANK YOU SO MUCH! It helped a LOT. Very well writed article.
About the reconnect, you could use a keepaliveloop to keep connected an prevent reconnect: /* create and start the keepalive */ keepAlive = new Timer(100000); keepAlive.addEventListener(TimerEvent.TIMER, onKeepAliveLoop); keepAlive.start();
/* define handle function */ private function onKeepAliveLoop(evt:TimerEvent):void { connection.sendKeepAlive(); //Alert.show(“keepalive ” + getTimer()); }
This works well for me.
Anthony said,
February 14, 2008 at 22:43
Thanks for this information Graham. I was particularly interested in your auto-reconnect code. I added it to my error handler and then went into debug mode.
Problem was that I never see (and have never seen) an error code 503 in my app, which would trigger this auto reconnect.
My question is: what triggers a 503 error? Based on the explanation above I would imagine it to be when the internet connection is interrupted to the client. However when I unplug my machine from the network no error gets triggered or anything
So I am trying to figure out what generates the 503, but more importantly, how to reliably auto-reconnect when things go wrong.
[graham] It’s dispatched in XMPPConnection.as, when it gets an IOError on the underlying socket. It should fire when you unplug your network cable, unless your XMPP server is local! Maybe it sends a DisconnectionEvent instead.
Graham King » Technologies for better web based applications: XMPP, Flex, and more said,
January 27, 2008 at 21:39
[…] There is an XMPP library for Flex, called XIFF. I’ve blogged some documentation about using XIFF. […]
Michael greene said,
January 22, 2008 at 02:19
Thanks Graham, I haven’t tested to make sure your comments helped, but that is a very well-written article on how to set it up and the specific problem I was having — the security sandbox violation — is covered right away in the “Permission to connect” section.
And since the other posts on your front page are on Python, Flickr, Gallery2, and unit testing, you have a new subscriber.
Side note: two + four = six, not 6.
shunjie said,
January 6, 2008 at 00:34
Thanks, I think thats a very comprehensive list ;)