Something Bitter In Iittala Class
This Iittala bowl contains something sweet but something bitter

XIFF has now support for client Software Version extension

Yet another new feature has landed to XIFF, the Actionscript 3 XMPP Instant Messaging Client Library. This feature follows the specification of XEP-0092: Software Version.

Example code below uses it to request version information from the people in the roster and responds if anyone will be asking for it.

/**
 * @mxmlc -target-player=10.0.0 -source-path=. -debug
 */
package
{
    import flash.display.*;
    import flash.events.*;
    import flash.text.*;
    import flash.utils.*;
    import flash.system.Security;
    import flash.ui.Keyboard;

    import org.igniterealtime.xiff.conference.*;
    import org.igniterealtime.xiff.core.*;
    import org.igniterealtime.xiff.data.*;
    import org.igniterealtime.xiff.data.im.*;
    import org.igniterealtime.xiff.events.*;
    import org.igniterealtime.xiff.exception.*;
    import org.igniterealtime.xiff.im.*;
    import org.igniterealtime.xiff.util.*;
    import org.igniterealtime.xiff.data.version.SoftwareVersionExtension;

    [SWF(backgroundColor = '0x11262B', frameRate = '33', width = '800', height = '800')]

    /**
     * Example of using Software Version Extension to see which clients are used by the people in roster.
     * @license http://creativecommons.org/licenses/by-sa/3.0/
     * @author Juga Paazmaya
     * @see http://paazmaya.fi/xiff-has-now-support-for-client-software-version-extension
     */
    public class XIFFExampleSoftwareVersion extends Sprite
    {
        private const SERVER:String = "192.168.1.37";
        private const PORT:uint = 5222;
        private const USERNAME:String = "flasher";
        private const PASSWORD:String = "flasher";
        private const RESOURCE_NAME:String = "SoftwareVersion";
        private const CHECK_POLICY:Boolean = true;
        private const POLICY_PORT:uint = 5229;

        private var _connection:XMPPConnection;
        private var _roster:Roster;

        private var _keepAlive:Timer;
        private var _outputField:TextField;
        private var _inputField:TextField;
        private var _recipientField:TextField;
        private var _rosterCont:Sprite;
        private var _chattingWith:UnescapedJID;
        private var _trafficField:TextField;

        public function XIFFExampleSoftwareVersion()
        {
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;

            loaderInfo.addEventListener(Event.INIT, onInit);
        }

        private function onInit(event:Event):void
        {
            if (CHECK_POLICY)
            {
                Security.loadPolicyFile("xmlsocket://" + SERVER + ":" + POLICY_PORT);
            }
            createElements();

            initChat();
        }

        private function createElements():void
        {
            _rosterCont = new Sprite();
            _rosterCont.x = 2;
            _rosterCont.y = 2;
            addChild(_rosterCont);

            _trafficField = createField("trafficField", 2, stage.stageHeight / 2,
                stage.stageWidth - 4, stage.stageHeight / 2 - 2);
            addChild(_trafficField);

            _recipientField = createField("recipientField", 2, 104,
                stage.stageWidth - 4, 20);
            addChild(_recipientField);

            _outputField = createField("outputField", 2, _recipientField.y + _recipientField.height + 2,
                stage.stageWidth - 4, stage.stageHeight / 4);
            addChild(_outputField);

            _inputField = createField("inputField", 10,
                _outputField.y + _outputField.height + 10,
                stage.stageWidth - 20, 40);
            _inputField.type = TextFieldType.INPUT;
            _inputField.maxChars = 160;
            _inputField.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
            addChild(_inputField);

        }

        private function initChat():void
        {
            _connection = new XMPPConnection();
            _connection.addEventListener(XIFFErrorEvent.XIFF_ERROR, onXiffError);
            _connection.addEventListener(IncomingDataEvent.INCOMING_DATA, onIncomingData);
            _connection.addEventListener(LoginEvent.LOGIN, onLogin);
            _connection.addEventListener(MessageEvent.MESSAGE, onMessage);
            _connection.addEventListener(OutgoingDataEvent.OUTGOING_DATA, onOutgoingData);
            _connection.addEventListener(PresenceEvent.PRESENCE, onPresence);
            _connection.addEventListener(SoftwareVersionExtension.NS, onSoftwareVersionRequest);

            _connection.enableExtensions(SoftwareVersionExtension);

            _connection.username = USERNAME;
            _connection.password = PASSWORD;
            _connection.server = SERVER;
            _connection.port = PORT;
            _connection.resource = RESOURCE_NAME;
            _connection.connect();

            _keepAlive = new Timer(2 * 60 * 1000);
            _keepAlive.addEventListener(TimerEvent.TIMER, onKeepAliveLoop);

            _roster = new Roster(_connection);
            _roster.addEventListener(RosterEvent.ROSTER_LOADED, onRoster);
            _roster.addEventListener(RosterEvent.SUBSCRIPTION_REQUEST, onRoster);
            _roster.addEventListener(RosterEvent.USER_AVAILABLE, onRoster);
        }

        private function messageSend():void
        {
            var txt:String = _inputField.text;
            var message:Message = new Message(_chattingWith.escaped, null, txt, null, Message.TYPE_CHAT);
            if (_connection.loggedIn)
            {
                _connection.send(message);
                message.from = _connection.jid.escaped;
                _inputField.text = "";
                addMessage(message);
            }
        }

        private function buildRosterContent():void
        {
            // Add buttons for each user found in the roster.
            var people:Array = _roster.toArray();
            var len:uint = people.length;
            for (var i:uint = 0; i < len; ++i)
            {
                var itemVO:RosterItemVO = people[ i ] as RosterItemVO;
                var item:Sprite = createButton(itemVO);
                item.x = _rosterCont.width + 4;
                _rosterCont.addChild(item);
            }
        }

        /**
         * Assuming that the whole roster has been loaded, now request the additional information
         */
        private function querySoftwareVersion(jid:EscapedJID):void
        {
            var iq:IQ = new IQ(jid, IQ.TYPE_GET);
            iq.callback = querySoftwareVersionCallback;
            iq.addExtension(new SoftwareVersionExtension());
            _connection.send(iq);
        }

        private function querySoftwareVersionCallback(iq:IQ):void
        {
            trace("querySoftwareVersionCallback. id: " + iq.id);
            var ext:SoftwareVersionExtension = iq.getExtension(SoftwareVersionExtension.ELEMENT_NAME) as SoftwareVersionExtension;
            var sp:Sprite = _rosterCont.getChildByName(iq.from.bareJID) as Sprite;
            if (sp != null)
            {
                var txVer:TextField = sp.getChildByName("version") as TextField;
                if (txVer != null)
                {
                    txVer.text = ext.name + " " + ext.version;
                }
            }
        }

        private function onSoftwareVersionRequest(event:IQEvent):void
        {
            var ext:SoftwareVersionExtension = event.data as SoftwareVersionExtension;
            if (event.iq.type == IQ.TYPE_GET)
            {
                var iq:IQ = new IQ(event.iq.from, IQ.TYPE_RESULT, event.iq.id);
                ext.name = "Paazmaya Client (XIFF)";
                ext.version = "0.2 (3.1.0)";
                ext.os = "Ubuntu Linux 12.04";
                iq.addExtension(ext);
                _connection.send(iq);
            }
        }

        private function createButton(item:RosterItemVO):Sprite
        {
            trace("createButton. " + item.jid.bareJID);
            var sp:Sprite = new Sprite();
            sp.name = item.jid.toString();
            sp.mouseChildren = false;
            sp.buttonMode = true;

            var tx:TextField = new TextField();
            tx.name = "jid";
            tx.text = item.jid.toString();
            tx.textColor = 0xF2F2F2;
            tx.autoSize = TextFieldAutoSize.LEFT;
            tx.y = 2;
            tx.x = 2;
            sp.addChild(tx);

            var txVer:TextField = new TextField();
            txVer.name = "version";
            txVer.text = "Software Version...";
            txVer.textColor = 0xF2F2F2;
            txVer.autoSize = TextFieldAutoSize.LEFT;
            txVer.y = 24;
            txVer.x = 2;
            sp.addChild(txVer);

            var gr:Graphics = sp.graphics;
            gr.beginFill(0x961D18);
            gr.lineStyle(1, 0x1A1A1A);
            gr.drawRoundRect(0, 0, sp.width + 4, 22, 8, 8);
            gr.endFill();

            sp.addEventListener(MouseEvent.CLICK, onMouse);
            sp.addEventListener(MouseEvent.MOUSE_OVER, onMouse);
            sp.addEventListener(MouseEvent.MOUSE_OUT, onMouse);
            return sp;
        }

        private function addMessage(message:Message):void
        {
            var date:Date = new Date();
            _outputField.appendText("[" + date.getHours() + ":"
                + date.getMinutes() + " | " + message.from.node + " > "
                + message.to.node + "] " + message.body + "\n");
            _outputField.scrollV = _outputField.maxScrollV;
        }

        private function onKeyUp(event:KeyboardEvent):void
        {
            switch(event.keyCode)
            {
                case Keyboard.ENTER :
                    messageSend();
                    break;
            }
        }

        private function onXiffError(event:XIFFErrorEvent):void
        {
            trace("onXiffError. " + event.toString());
            trace("onXiffError.errorMessage: " + event.errorMessage);
        }

        private function onIncomingData(event:IncomingDataEvent):void
        {
            trace("onIncomingData. " + event.data.toString());
            _trafficField.appendText(getTimer() + " - incoming: " +
                event.data.toString() + "\n\n");
        }

        private function onOutgoingData(event:OutgoingDataEvent):void
        {
            _trafficField.appendText(getTimer() + " - outgoing: " +
                event.data.toString() + "\n\n");
        }

        private function onLogin(event:LoginEvent):void
        {
            trace("onLogin. " + event.toString());

            _keepAlive.start();

            var presence:Presence = new Presence(null, _connection.jid.escaped, null, null, null, 1);
            _connection.send(presence);
            trace("Sending presence: " + presence);
        }

        private function onRoster(event:RosterEvent):void
        {
            trace("onRoster. " + event.toString());
            switch (event.type)
            {
                case RosterEvent.ROSTER_LOADED :
                    buildRosterContent();
                    break;
                case RosterEvent.SUBSCRIPTION_REQUEST :
                    // If the JID is in the roster, accept immediately
                    if (_roster.getPresence(event.jid) != null)
                    {
                        _roster.grantSubscription(event.jid, true);
                    }
                    break;
                case RosterEvent.USER_AVAILABLE :
                    // data should be Presence
                    var presence:Presence = event.data as Presence;
                    if (presence != null)
                    {
                        querySoftwareVersion(presence.from);
                    }
                    break;
            }
        }

        private function onMessage(event:MessageEvent):void
        {
            trace("onMessage. " + event.toString());
            addMessage(event.data);
        }

        private function onPresence(event:PresenceEvent):void
        {
            trace("onPresence. " + event.toString());
            var len:uint = event.data.length;
            for (var i:uint = 0; i < len; ++i)
            {
                var presence:Presence = event.data[i] as Presence;
                trace("onPresence. " + i + " show: " + presence.show);
                trace("onPresence. " + i + " type: " + presence.type);
                trace("onPresence. " + i + " status: " + presence.status);
                trace("onPresence. " + i + " from: " + presence.from);
                trace("onPresence. " + i + " to: " + presence.to);

                switch (presence.type)
                {
                    case Presence.TYPE_SUBSCRIBE :
                        // Automatically add all those to _roster whom have requested to be our friend.
                        _roster.grantSubscription(presence.from.unescaped, true);
                        break;
                    case Presence.TYPE_SUBSCRIBED :
                        break;
                }
            }
        }

        private function onMouse(event:MouseEvent):void
        {
            var sp:Sprite = event.target as Sprite;
            var tx:TextField = sp.getChildAt(0) as TextField;
            switch (event.type)
            {
                case MouseEvent.CLICK :
                    _chattingWith = new UnescapedJID(sp.name);
                    _recipientField.text = _chattingWith.bareJID.toString();

                    var ros:RosterItemVO = RosterItemVO.get(_chattingWith);
                    trace(">> Roster data for " + _chattingWith.toString());
                    trace(">> nickname: " + ros.nickname);
                    trace(">> askType: " + ros.askType);
                    trace(">> priority: " + ros.priority);
                    trace(">> show: " + ros.show);
                    trace(">> pending: " + ros.pending);
                    trace(">> status: " + ros.status);
                    trace(">> subscribeType: " + ros.subscribeType);
                    trace(">> uid: " + ros.uid);

                    // Could get the presence like this
                    trace("JID: " + sp.name + ", Presence: "
                        + _roster.getPresence(new UnescapedJID(sp.name)));

                    break;
                case MouseEvent.MOUSE_OVER :
                    tx.textColor = 0xFF40A1;
                    break;
                case MouseEvent.MOUSE_OUT :
                    tx.textColor = 0xF2F2F2;
                    break;
            }
        }

        private function onKeepAliveLoop(event:TimerEvent):void
        {
            _connection.sendKeepAlive();
        }

        private function createField(name:String, xPos:Number,
            yPos:Number, w:Number, h:Number):TextField
        {
            var format:TextFormat = new TextFormat("Verdana", 12, 0x121212);
            var bgColor:uint = 0xE3E3C9;
            var borderColor:uint = 0x961D18;

            var field:TextField = new TextField();
            field.name = name;
            field.defaultTextFormat = format;
            field.background = true;
            field.backgroundColor = bgColor;
            field.border = true;
            field.borderColor = borderColor;
            field.multiline = true;
            field.wordWrap = true;
            field.mouseWheelEnabled = true;
            field.x = xPos;
            field.y = yPos;
            field.width = w;
            field.height = h;

            return field;
        }
    }
}

This new feature is now available in the trunk version of XIFF and later this month in the XIFF 3.1.0 release.