Categories
Articles

Adobe AIR (P2P) Peer To Peer Text Chat with NetGroup

By Lon (Alonzo) Hosford

This is a minimalist example of an Adobe AIR peer to peer text chat application using the NetGroup class for peer communication. Create an new AIR application and replace all the code with the code below.

[ad name=”Google Adsense”]

You need to change line 15 to include your own Adobe Stratus developer key. You can use the Adobe Stratus server by obtaining a developer key at Adobe Labs.

On line 16 you need to create a name for your group. You want something unique so consider a reverse domain prefix such as com.yourdomain plus an application name and something unique. For example com.bugsbunny.carrot-chat.b7-4d;8k9.

You can also make this a Flex application. Create a flex application and paste all but the WindowedApplication MXML tags between your Application MXML tags.

The application starts once the user clicks the connect button. This establishes a connection with the Adobe Stratus server on line 51. Once the connection is successful, ncNetStatus(…) handler receives a NetConnection.Connect.Success event code and calls the setupGroup() method on line 100. The setupGroup() method creates the requisite specification to join the group you identified in line 16.

Once the user selects the user name and clicks connect, the user name cannot be changed. That is an application design choice on line 190 with the enabled property for the user_ti TextInput component. However you might notice the new Flex 4 two way binding used for the userName variable and the user_ti TextInput component text property on line 190. If you want to change the user name after the connection just remove the enabled property on line 190.

.
.
.
<?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"
					   creationComplete="creationCompleteHandler(event)"
					   height="600" width="800"
					   >
	<fx:Script>
		<![CDATA[
			import mx.events.FlexEvent;
			private const SERVER:String = "rtmfp://stratus.adobe.com/";
			private const DEVKEY:String = "{YOUR ADOBE STRATUS DEVELOPER KEY}";
			private const NETGROUP:String = "{YOUR NETGROUP NAME}";
			private var _nc:NetConnection;
			private var _netGroup:NetGroup;

			// NetGroup specifier for NETGROUP
			[Bindable]
			private var groupSpecWithAuthorizations:String;

			// Connected to Stratus server and to NETGROUP
			[Bindable]
			private var _connected:Boolean = false;

			// _userName name in chat.
			[Bindable]
			private var _userName:String;

			// Used to store our P2P Peer ID for binding to UI.
			[Bindable]
			private var _nearID:String;

			// Counter to make every NetGroup post message unique
			private var _msgOutCount:int = 0;

			/**
			 * CreationComplete event handler for WindowedApplication
			 * */
			protected function creationCompleteHandler(event:FlexEvent):void
			{
				console (className + ".creationCompleteHandler(...) - Capabilities.version:" + Capabilities.version);
				// Generate a default user name;
				_userName = "user" +  Math.round(Math.random()*10000);
			}
			/**
			 * Connect with Stratus server
			 * */
			private function connect():void
			{
				_nc = new NetConnection();
				_nc.addEventListener(NetStatusEvent.NET_STATUS,ncNetStatus);
				_nc.connect(SERVER+DEVKEY);
			}
			/**
			 * NetStatusEvent.NET_STATUS event handler for _nc and _netGroup
			 * */
			private function ncNetStatus(event:NetStatusEvent):void
			{
				console( className + ".ncNetStatus(...) - event.info.code:" + event.info.code);

				switch(event.info.code)
				{
					case "NetConnection.Connect.Success":
						_nearID = _nc.nearID;// or you can use event.target.nearID;
						setupGroup();
						break;
					case "NetGroup.Connect.Success":
						_connected = true;
						break;
					case "NetGroup.Posting.Notify":
						receivePostNotifyMessage(event.info.message);
						break;
					// FYI: More NetGroup event info codes
					case "NetGroup.Neighbor.Connect":
					case "NetGroup.LocalCoverage.Notify":
					case "NetGroup.SendTo.Notify": // event.info.message, event.info.from, event.info.fromLocal
					case "NetGroup.MulticastStream.PublishNotify": // event.info.name
					case "NetGroup.MulticastStream.UnpublishNotify": // event.info.name
					case "NetGroup.Replication.Fetch.SendNotify": // event.info.index
					case "NetGroup.Replication.Fetch.Failed": // event.info.index
					case "NetGroup.Replication.Fetch.Result": // event.info.index, event.info.object
					case "NetGroup.Replication.Request": // event.info.index, event.info.requestID
					default:
					{
						break;
					}
				}
			}

			// ========================================
			//  NETGROUP Methods
			// ========================================

			/**
			 * Connect with the NETGROUP
			 * */
			private function setupGroup():void
			{
				console( className + ".setupGroup()");
				var groupspec:GroupSpecifier = new GroupSpecifier(NETGROUP);
				// Allow group members to open channels to server
				groupspec.serverChannelEnabled = true;
				// Allow group members to post
				groupspec.postingEnabled = true;

				// Create the group specifi
				groupSpecWithAuthorizations = groupspec.groupspecWithAuthorizations();

				// Join the group specified by groupspec
				_netGroup = new NetGroup(_nc, groupSpecWithAuthorizations);

				// Register listener for NetGroup NetStatus events
				_netGroup.addEventListener(NetStatusEvent.NET_STATUS, ncNetStatus);
			}
			/**
			 * Post a message to NETGROUP;
			 * @param messageText String. Text message to send.
			 * */
			private function sendMessageToGroup(messageText:String):void
			{
				console( className + ".sendMessageToGroup(...) - messageText:" + messageText);
				// Construct message object
				var netGroupMessage:Object = new Object();
				netGroupMessage.sender = _netGroup.convertPeerIDToGroupAddress(_nc.nearID);
				netGroupMessage.user = _userName;
				netGroupMessage.text = messageText;
				netGroupMessage.sequence = ++ _msgOutCount;// Only unique message objects are sent.
				// Send netGroupMessage object to all members of the NETGROUP
				_netGroup.post(netGroupMessage);
			}
			/**
			 * Receive a NetGroup.Posting.Notify message from NETGROUP
			 * @param netGroupMessage Object. NetGroup post message object.
			 * */
			private function receivePostNotifyMessage(netGroupMessage:Object):void
			{
				updateUI(netGroupMessage.user, netGroupMessage.text)
 			}

			// ========================================
			//  UI Methods
			// ========================================

			/**
			 * Join chat group
			 * */
			private function joinChat():void
			{
				// Connect to server
				connect();
			}
			/**
			 * Post a message to NETGROUP;
			 * */
			private function sendMessage():void
			{
				// Send message to NetGroup
				sendMessageToGroup(message_ti.text);
				// Update local view of message sent
				updateUI(_userName, message_ti.text);
			}
			/**
			 * Update UI with message object received from NETGROUP
			 * @param userName String. Name of user sending message.
			 * @param messageText String. Text of message to sent.
			 * */
			private function updateUI(userName:String, messageText:String):void
			{
				chat_ta.appendText( userName + ": " + messageText + "\n");
			}

			private function console(msg:String):void
			{
				trace(msg);
				out_ta.appendText(msg + "\n");
			}
		]]>
	</fx:Script>
	<s:layout >
		<s:VerticalLayout horizontalAlign="left"
						  paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="10"/>
	</s:layout>
	<!--UI For Chat-->
	<s:Label text = "Chat log:" />
	<s:TextArea id="chat_ta" height = "120" width="100%"/>
	<s:HGroup width="100%">
		<s:TextInput  id="user_ti" text="@{_userName}" enabled="{!_connected}"/>
		<s:Button label="Connect" click="joinChat()" enabled="{!_connected}" />
		<s:TextInput  id="message_ti"  width="70%" enter="sendMessage()" enabled="{_connected}"/>
		<s:Button label="Send" click="sendMessage()" enabled="{_connected}" />
		<s:Button label="Clear" click="chat_ta.text = '';"   />

	</s:HGroup>

	<!--UI For Tracing P2P-->
	<s:Label text = "P2P Information" paddingTop="20"/>

	<s:HGroup horizontalAlign="left" width="100%" >
		<s:Label text = "Peer Id: " />
		<s:Label id = "peerId_lbl" text = "{_nearID}"/>
	</s:HGroup>

	<s:HGroup horizontalAlign="left" width="100%" >
		<s:Label text = "Group Id: " />
		<s:Label id = "groupId_lbl" text = "{groupSpecWithAuthorizations}" />
	</s:HGroup>

	<s:Label text = "Trace log:" paddingTop="20"/>

	<s:TextArea id="out_ta"  height = "250" width="100%" lineBreak="explicit" fontFamily="_typewriter"/>
	<s:HGroup horizontalAlign="center" width="100%">
		<s:Button label="Clear" click="out_ta.text = '';"   />
	</s:HGroup>
</s:WindowedApplication>

I had experienced some problems with launching the application in Eclipse Flex Builder 4. I had published the application and launched on other computers. The Flex Builder 4 launch would not post to the NetGroup or receive from it if launched before other application instances on other machines. However if it was launched after another application instance was launched, it seemed to post and receive posts.