Monday 1 November 2010

AIR app socket connection to Socket.IO-node server using Web-socket-js flash library

UPDATE 2012 - This article is outdated  please use https://github.com/simb/FlashSocket.IO for connecting Socket IO and Flash/Flex/AS3

Socket.IO is an awesome library for NodeJS for making  games and chat apps relying on WebSocket protocol. Web browsers supporting WebSockets at the time of this writing are Chrome, Safari and Firefox 4 beta, and Opera 10.70 beta Other browsers can use WebSocket over Flash or other methods (XHR Pooling, XHR Streaming, forever IFrame). In this article I will show you how I connected AIR app to Socket.IO-Node chat demo using WebSocket over Flash library made by Hiroshi Ichikawa.
Requirements:
- for the Server
UPDATE - github source -> here



This article is not about setting up server side. You can get enough information on NodeJS and Socket.io websites. I will show you how to connect AIR app to Socket.IO server. Create new AIR Flex 4 project in Flash Develop. Make some simple GUI layout  and save it as FlexMain.mxml

<?xml version="1.0" encoding="utf-8"?>

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"

   xmlns:s="library://ns.adobe.com/flex/spark"

   xmlns:mx="library://ns.adobe.com/flex/mx"

    backgroundColor="#FFFFFF"

    backgroundAlpha="0"

creationComplete="init();">



<mx:VBox width="100%" height="500">

<mx:List id="messages" width="100%" height="400" dataProvider="{msgBuffer}" />

<mx:HBox>

<mx:TextInput id="msgbox" height="50" width="650"  keyDown="msgboxKeyDown(event)" />

<mx:Button id="btn" click="sendMessage()" height="50" width="50" />

</mx:HBox>

</mx:VBox>

</s:WindowedApplication>

Get the web-socket-js latest version with Git subversion control system and copy WebSocket.as, WebSocketStateEvent.as and com folder to your project /src folder.
source_folder

Get as3corelib from github and copy this folder as3corelib\src\com\adobe\serialization to your src\com\adobe folder in your project. It contains JSON classes for parsing, encoding and decoding. Now your \src folder have to looks like this

source_folder2


Now go to your FlexMain.mxml file and add new fx:Script tag just above <mx:VBox> tag.

<fx:Script>

              <![CDATA[

import flash.events.*;

import flash.net.*;

import flash.system.*;

import flash.utils.*;

import WebSocket;

import com.adobe.serialization.json.*;



import mx.collections.ArrayCollection;



[Bindable]

private var msgBuffer: ArrayCollection;



private var websock:WebSocket;

private var sessionId:String;

private var frame:String = '~m~';



private function init():void {

msgBuffer = new ArrayCollection([]);

sessionId = null;

loadPolicyFile("xmlsocket://192.168.1.16:843");

websock = new WebSocket(this, "ws://192.168.1.16:8080/socket.io/flashsocket","");

websock.addEventListener("open", onOpen);

websock.addEventListener("close", onClose);

websock.addEventListener("message", onMessage);

}

public function onMessage(e:Event):void

{





var msgs:Array = websock.readSocketData();



for each (var msg:String in msgs) {

msg = decodeURIComponent(msg);

  log("received string: "+msg);

  if (msg.substr(0, 3) == frame)

  {

  var m:Array = msg.split(frame);

  if (sessionId == null) {

  sessionId = m[2];

  log("session id= " + sessionId);

  }

  else

  {

log(" m[2] = " +m[2]);

if (m[2].substr(0, 3) == '~h~')

{

log("heartbeat: " + msg);

sendHeartBeat(m[2].substr(3));

}

else

if (m[2].substr(0, 3) == '~j~')

{

log("json: " + msg);

parseMsg(m[2].substr(3));

}

else

{

parseMsg(m[2]);

    log ("message: " + m[2]);

}  

  }

  }



}

}

private function parseMsg(s:String):void

{

var msg:Object = JSON.decode(s);



if (msg.announcement != null) {

trace("announcement :" + msg.announcement);

addToList(msg.announcement);

}

else

if (msg.message != null)

{

addToList(msg.message[0]+" :"+msg.message[1]);

}

else



if (msg.buffer != null) {

for each (var o:Object in msg.buffer) {

addToList(o.message[0]+" : "+o.message[1]);

}

}

}



private function addToList(s:String):void {

msgBuffer.addItem(s);

messages.verticalScrollPosition = messages.maxVerticalScrollPosition;

}



public function sendMsg(m:String):void

{

websock.send(frame + m.length + frame + m);

addToList("me : "+m);

}



public function sendHeartBeat(num:String):void

{

var strnum:int = 3 + num.length;

websock.send(frame + strnum.toString() + frame+ '~h~' + num);

}

public function onClose(e:Event):void

{

log("websocket disconnected");

}



public function onOpen(e:Event):void

{

log("websocket connected");

//websock.send(frame+'17'+frame+"USERNAME:username");

}





private function sendMessage():void {

sendMsg(msgbox.text);

msgbox.text="";

}

public function getCallerHost():String {

return ("http://192.168.1.16");

}

  

public function getOrigin():String {

return ("http://192.168.1.16");

}



public function fatal(str:String):void

{

trace(str);

}



public function error(str:String):void

{

trace(str);

}



public function log(str:String):void

{

trace(str);

}



public function loadPolicyFile(url:String):void

{

log("policy file: " + url);

Security.loadPolicyFile(url);

}



public function getCookie():String

{

return sessionId;

}



private function msgboxKeyDown(event:KeyboardEvent):void

{

                if (event.keyCode == Keyboard.ENTER)

sendMessage();

}





              ]]>

       </fx:Script>
In original WebSocket.as file the author use callback functions to main project
file to exchange log, errors and other messages. The name of the original  
main project file is called WebSocketMain.as.To make it working with my flex app
I just named it to plain Object reference. So open WebSocket.as file and 
search for WebSocketMain and replace it with Object
Its marked with big red bold font in the source.

// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>

// License: New BSD License

// Reference: http://dev.w3.org/html5/websockets/

// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76



package {



import flash.display.*;

import flash.events.*;

import flash.external.*;

import flash.net.*;

import flash.system.*;

import flash.utils.*;

import mx.core.*;

import mx.controls.*;

import mx.events.*;

import mx.utils.*;

import com.adobe.net.proxies.RFC2817Socket;

import com.hurlant.crypto.tls.TLSSocket;

import com.hurlant.crypto.tls.TLSConfig;

import com.hurlant.crypto.tls.TLSEngine;

import com.hurlant.crypto.tls.TLSSecurityParameters;

import com.gsolo.encryption.MD5;



[Event(name="message", type="flash.events.Event")]

[Event(name="open", type="flash.events.Event")]

[Event(name="close", type="flash.events.Event")]

[Event(name="error", type="flash.events.Event")]

[Event(name="stateChange", type="WebSocketStateEvent")]

public class WebSocket extends EventDispatcher {



  private static var CONNECTING:int = 0;

  private static var OPEN:int = 1;

  private static var CLOSING:int = 2;

  private static var CLOSED:int = 3;



  private var rawSocket:Socket;

  private var tlsSocket:TLSSocket;

  private var tlsConfig:TLSConfig;

  private var socket:Socket;

  private var main:Object;

  private var url:String;

  private var scheme:String;

  private var host:String;

  private var port:uint;

  private var path:String;

  private var origin:String;

  private var protocol:String;

  private var buffer:ByteArray = new ByteArray();

  private var dataQueue:Array;

  private var headerState:int = 0;

  private var readyState:int = CONNECTING;

  private var bufferedAmount:int = 0;

  private var headers:String;

  private var noiseChars:Array;

  private var expectedDigest:String;



  public function WebSocket(

      main:Object, url:String, protocol:String,

      proxyHost:String = null, proxyPort:int = 0,

      headers:String = null) {
Save it and you can compile the AIR app.
To test it you need to run the socket.io-node example server. It’s in the folder example. Go there and run it with # node server.js command.
Set your correct network location to the server in my case it’s 192.158.1.16. change it with your server domain, ip address or localhost. The default port of socket.io-node’s example is 8080. but u can change it if you have your own socket-io based server.
This is the final result.
airapp

thanks for reading!