├── examples ├── libs │ ├── p2ploc.swc │ ├── tb_board.swc │ └── tb_utils_lib.swc ├── p2p_wb │ ├── p2p_wb.swf │ ├── README.md │ ├── index.html │ ├── p2p_wb.iml │ └── src │ │ └── wb.mxml ├── p2p_chat_starling │ ├── .settings │ │ └── org.eclipse.core.resources.prefs │ ├── .project │ ├── src │ │ ├── Chat.as │ │ ├── ChatMain.as │ │ └── Chat-app.xml │ └── .actionScriptProperties ├── p2p_chat │ ├── README.md │ ├── p2p_chat.iml │ └── src │ │ └── chat.mxml └── index.html ├── LICENSE ├── p2ploc.iml ├── README.md └── src └── com └── greygreen └── net └── p2p ├── model ├── P2PConfig.as └── P2PPacket.as ├── events └── P2PEvent.as ├── P2PMailbox.as └── P2PClient.as /examples/libs/p2ploc.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palkan/as3_p2plocal/HEAD/examples/libs/p2ploc.swc -------------------------------------------------------------------------------- /examples/libs/tb_board.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palkan/as3_p2plocal/HEAD/examples/libs/tb_board.swc -------------------------------------------------------------------------------- /examples/p2p_wb/p2p_wb.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palkan/as3_p2plocal/HEAD/examples/p2p_wb/p2p_wb.swf -------------------------------------------------------------------------------- /examples/libs/tb_utils_lib.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palkan/as3_p2plocal/HEAD/examples/libs/tb_utils_lib.swc -------------------------------------------------------------------------------- /examples/p2p_chat_starling/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | #Mon Jan 20 17:35:44 GMT 2014 2 | eclipse.preferences.version=1 3 | encoding/=utf-8 4 | -------------------------------------------------------------------------------- /examples/p2p_chat/README.md: -------------------------------------------------------------------------------- 1 | ## P2P local chat 2 | 3 | ### Features: 4 | 5 | * Public chat with history 6 | * Active users list 7 | * Private chat (with direct_routing) 8 | -------------------------------------------------------------------------------- /examples/p2p_wb/README.md: -------------------------------------------------------------------------------- 1 | ## Synchronized Whiteboard Example 2 | 3 | ### How it works: 4 | 5 | * Run one instance of application. Draw something 6 | * Run another instance within the same network and you will see (after few seconds of loading) the same drawing. From that point all actions with whiteboard would be synchronized. 7 | 8 | _P.S. Of course, this app is just example and not safe from simultaneous editing problems)_ -------------------------------------------------------------------------------- /examples/p2p_wb/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | as3_p2plocal – whiteboard 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | as3_p2plocal – examples 5 | 6 | 7 | 30 | 31 | 32 |

Examples

33 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 palkan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /examples/p2p_chat_starling/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | StarlingFeathersTest 4 | 5 | 6 | 7 | 8 | 9 | com.adobe.flexbuilder.project.flexbuilder 10 | 11 | 12 | 13 | 14 | com.adobe.flexbuilder.project.apollobuilder 15 | 16 | 17 | 18 | 19 | 20 | com.adobe.flexide.project.multiplatform.multiplatformasnature 21 | com.adobe.flexide.project.multiplatform.multiplatformnature 22 | com.adobe.flexbuilder.project.apollonature 23 | com.adobe.flexbuilder.project.actionscriptnature 24 | 25 | 26 | 27 | as3_p2plocal 28 | 2 29 | $%7BLIBRARY_LOC%7D/as3_p2plocal 30 | 31 | 32 | feathers 33 | 2 34 | $%7BLIBRARY_LOC%7D/feathers 35 | 36 | 37 | starling 38 | 2 39 | $%7BLIBRARY_LOC%7D/Starling-Framework 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /p2ploc.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AS3 local RTMFP connections library 2 | ---------- 3 | 4 | ### Features: 5 | 6 | * Create new local net group with just one call using config Object 7 | * Message types 8 | * Direct routing (peer-to-peer send message) 9 | * Automatically restore session state (if there is at least one another peer in the group) 10 | 11 | Examples: [http://palkan.github.io/as3_p2plocal/examples/index.html](http://palkan.github.io/as3_p2plocal/examples/index.html) 12 | 13 | ### Usage 14 | 15 | ```actionscript 16 | 17 | // ---------- init ------------// 18 | 19 | p2p_client = new P2PClient(); //create new instance 20 | 21 | p2p_client.addEventListener(P2PEvent.CONNECTED, onConnect); // add listener for 'connect' event: it means connection established and you are connected to NetGroup; if you don't need to restore a state - that's enough 22 | p2p_client.addEventListener(P2PEvent.STATE_RESTORED, onStateRestored); // add listener for 'state_restored' event: it means all previously dispatched messages within the group has been received and ready to be parsed; 23 | 24 | p2p_client.listen(messageReceived, "message"); // add listener for messages of a type "message" 25 | 26 | p2p_client.connect(new P2PConfig({ 27 | groupName: "example", // NetGroup name 28 | saveState: "true" // restore state from previous messages 29 | })); 30 | 31 | //------------- state restored ---------// 32 | 33 | function onStateRestored(e:P2PEvent):void { 34 | // e.info.state contains an Array of messages 35 | } 36 | 37 | //------------ receive messages --------// 38 | 39 | function messageReceived(p:P2PPacket):void{ 40 | // handle message; p.data contains data was sent 41 | } 42 | 43 | //------------ send messages ----------// 44 | 45 | p2p_client.send(data, type(="default"), system (=true), recipient(=""); 46 | 47 | ``` 48 | -------------------------------------------------------------------------------- /examples/p2p_chat/p2p_chat.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/com/greygreen/net/p2p/model/P2PConfig.as: -------------------------------------------------------------------------------- 1 | /** 2 | * User: palkan 3 | * Date: 8/20/13 4 | * Time: 2:52 PM 5 | */ 6 | package com.greygreen.net.p2p.model { 7 | import flash.errors.IllegalOperationError; 8 | 9 | import ru.teachbase.utils.Strings; 10 | 11 | 12 | /** 13 | * 14 | * P2P connection configuration. 15 | * 16 | * Available properties: 17 | *
  • groupName:String = "__default__" - NetGroup name (id)
  • 18 | *
  • ip:String = "225.225.0.1:30303" - IP address for multicast. IP address should begin with 224 or higher and port number greater than 1024.
  • 19 | *
  • saveState:Boolean = false - define whether to use object replication to store packets. 20 | * 21 | * @see P2PPacket$ 22 | * 23 | */ 24 | 25 | public class P2PConfig { 26 | 27 | private var _groupName:String="__default__"; 28 | private var _ip:String = "225.225.0.1:30303"; 29 | private var _saveState:Boolean = false; 30 | 31 | /** 32 | * @throws IllegalOperationError Incorrect IP address provided. 33 | **/ 34 | 35 | public function P2PConfig(source:Object = null){ 36 | 37 | if(!source) return; 38 | 39 | for(var key:String in source){ 40 | this["_"+key] = Strings.serialize(source[key]); 41 | } 42 | 43 | validateIP(); 44 | 45 | } 46 | 47 | 48 | protected function validateIP():void{ 49 | 50 | const regexp:RegExp = /^\s*\d{3}\.\d{3}\.\d{1}\.\d{1}:(\d{4,})\s*$/; 51 | 52 | var matches:Array; 53 | 54 | if(matches = _ip.match(regexp)){ 55 | if(parseInt(matches[1]) > 1024) return; 56 | } 57 | 58 | throw new IllegalOperationError("Incorrect IP address provided."); 59 | } 60 | 61 | /** 62 | * Define whether to use object replication to store packets. 63 | */ 64 | 65 | public function get saveState():Boolean { 66 | return _saveState; 67 | } 68 | 69 | /** 70 | * IP address for multicast. 71 | */ 72 | 73 | public function get ip():String { 74 | return _ip; 75 | } 76 | 77 | /** 78 | * NetGroup name (id). 79 | */ 80 | 81 | public function get groupName():String { 82 | return _groupName; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /examples/p2p_wb/p2p_wb.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/com/greygreen/net/p2p/events/P2PEvent.as: -------------------------------------------------------------------------------- 1 | /** 2 | * User: palkan 3 | * Date: 8/20/13 4 | * Time: 2:07 PM 5 | */ 6 | package com.greygreen.net.p2p.events { 7 | import flash.events.Event; 8 | 9 | public class P2PEvent extends Event{ 10 | 11 | /** 12 | * Indicates that NetConnection and NetGroup connected successfully. 13 | * 14 | * info.group contains corresponding NetGroup object. 15 | * 16 | * @eventType p2p:connected 17 | */ 18 | 19 | public static const CONNECTED:String = "p2p:connected"; 20 | 21 | 22 | /** 23 | * Indicates that replicated session stated has been restored and client starting to dispatch new messages. 24 | * 25 | * Dispatches only if P2PConfig.saveState == true. 26 | * 27 | * info.state:Array contains history messages. 28 | * 29 | * @eventType p2p:state_restored 30 | */ 31 | 32 | public static const STATE_RESTORED:String = "p2p:state_restored"; 33 | 34 | /** 35 | * Indicates that error occured during state restore (maybe due to denial overhead). 36 | * 37 | * Dispatches only if P2PConfig.saveState == true. 38 | * 39 | * info is null 40 | * 41 | * @eventType p2p:state_restore_failed 42 | */ 43 | 44 | public static const STATE_RESTORE_FAILED:String = "p2p:state_restore_failed"; 45 | 46 | /** 47 | * 48 | * NetConnection or NetGroup failed to connect. 49 | * 50 | * info.message contains String representation of error. 51 | * 52 | * @eventType p2p:failed 53 | * 54 | */ 55 | 56 | public static const FAILED:String = "p2p:failed"; 57 | 58 | 59 | /** 60 | * 61 | * NetConnection was closed successfully. 62 | * 63 | * info is null. 64 | * 65 | * @eventType p2p:closed 66 | * 67 | */ 68 | 69 | public static const CLOSED:String = "p2p:closed"; 70 | 71 | /** 72 | * New peer connected as a neighbor. 73 | * 74 | * info.peerID contains the peer's peerID. 75 | * 76 | * @eventType p2p:peer_connected 77 | */ 78 | 79 | public static const PEER_CONNECTED:String = "p2p:peer_connected"; 80 | 81 | /** 82 | * Peer disconnected. 83 | * 84 | * info.peerID contains the peer's peerID. 85 | * 86 | * @eventType p2p:peer_disconnected 87 | */ 88 | 89 | public static const PEER_DISCONNECTED:String = "p2p:peer_disconnected"; 90 | 91 | 92 | private var _info:Object; 93 | 94 | public function P2PEvent(type:String, info:Object = null, bubbles:Boolean = false, cancelable:Boolean = false) { 95 | 96 | super(type,bubbles,cancelable); 97 | 98 | _info = info; 99 | 100 | } 101 | 102 | /** 103 | * @inheritDoc 104 | */ 105 | 106 | override public function clone():Event{ 107 | return new P2PEvent(type,info,bubbles,cancelable); 108 | } 109 | 110 | /** 111 | * Additional info. Depends on type. 112 | */ 113 | 114 | public function get info():Object { 115 | return _info; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/com/greygreen/net/p2p/model/P2PPacket.as: -------------------------------------------------------------------------------- 1 | /** 2 | * User: palkan 3 | * Date: 8/20/13 4 | * Time: 2:25 PM 5 | */ 6 | 7 | package com.greygreen.net.p2p.model { 8 | import ru.teachbase.utils.system.registerClazzAlias; 9 | 10 | 11 | /** 12 | * Packaging model for data messages. 13 | * 14 | */ 15 | 16 | registerClazzAlias(P2PPacket); 17 | 18 | 19 | 20 | public class P2PPacket extends Object{ 21 | 22 | 23 | protected var _type:String; 24 | 25 | protected var _system:Boolean; 26 | 27 | protected var _data:*; 28 | 29 | protected var _senderId:String; 30 | 31 | protected var _recipientId:String = ""; 32 | 33 | protected var _ts:Number; 34 | 35 | public function P2PPacket(type:String = "default", data:* = null, senderId:String = '', recipientId:String = '', system:Boolean = true) { 36 | 37 | _type = type; 38 | _data = data; 39 | _senderId = senderId; 40 | _recipientId = recipientId; 41 | _system = system; 42 | _ts = (new Date()).time; 43 | } 44 | 45 | /** 46 | * 47 | * Sender's NetConnection.nearId. 48 | * 49 | */ 50 | 51 | public function get senderId():String { 52 | return _senderId; 53 | } 54 | 55 | 56 | 57 | /** 58 | * 59 | * Define whether this message must be stored in P2PMailbox. 60 | * 61 | * @see P2PMailbox 62 | */ 63 | 64 | public function get system():Boolean { 65 | return _system; 66 | } 67 | 68 | 69 | 70 | /** 71 | * 72 | * Message type to differentiate messages. 73 | * 74 | */ 75 | 76 | public function get type():String { 77 | return _type; 78 | } 79 | 80 | 81 | 82 | /** 83 | * 84 | */ 85 | 86 | public function get data():* { 87 | return _data; 88 | } 89 | 90 | 91 | 92 | /** 93 | * 94 | * Recipient's peerID. 95 | * 96 | */ 97 | 98 | public function get recipientId():String { 99 | return _recipientId; 100 | } 101 | 102 | 103 | /** 104 | * 105 | * Sender's timestamp in ms. 106 | * 107 | */ 108 | 109 | 110 | public function get ts():Number { 111 | return _ts; 112 | } 113 | 114 | 115 | /** 116 | * 117 | * @param value 118 | * @private 119 | */ 120 | 121 | public function set senderId(value:String):void { 122 | _senderId = value; 123 | } 124 | 125 | 126 | /** 127 | * 128 | * @param value 129 | * @private 130 | */ 131 | 132 | public function set system(value:Boolean):void{ 133 | _system = value; 134 | } 135 | 136 | 137 | /** 138 | * 139 | * @param value 140 | * @private 141 | */ 142 | 143 | public function set type (value:String):void{ 144 | _type = value; 145 | } 146 | 147 | 148 | 149 | /** 150 | * 151 | * @param value 152 | * @private 153 | */ 154 | 155 | public function set data(value:*):void{ 156 | _data = value; 157 | } 158 | 159 | 160 | /** 161 | * 162 | * @param value 163 | * @private 164 | */ 165 | 166 | public function set recipientId(value:String):void{ 167 | _recipientId = value; 168 | } 169 | 170 | 171 | 172 | /** 173 | * 174 | * @param value 175 | * @private 176 | */ 177 | 178 | public function set ts(value:Number):void{ 179 | _ts = ts; 180 | } 181 | 182 | 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/com/greygreen/net/p2p/P2PMailbox.as: -------------------------------------------------------------------------------- 1 | /** 2 | * User: palkan 3 | * Date: 8/20/13 4 | * Time: 2:00 PM 5 | */ 6 | package com.greygreen.net.p2p { 7 | import com.greygreen.net.p2p.model.P2PPacket; 8 | 9 | import flash.net.NetGroup; 10 | 11 | public class P2PMailbox { 12 | 13 | private const storage:Array = []; 14 | 15 | private const temp:Array = []; 16 | 17 | private var _restoreWaiting:Boolean = false; 18 | 19 | private var _restored:Boolean = false; 20 | 21 | private var _expectedSize:Number = 0; 22 | 23 | private var _next:Number = 0; 24 | 25 | private var _group:NetGroup; 26 | 27 | public function P2PMailbox(group:NetGroup) { 28 | _group = group; 29 | } 30 | 31 | /** 32 | * 33 | * Add new message. 34 | * 35 | * @param packet 36 | * @param incrNext define whether to increment next message to dispatch counter. Prevents messaging loopback. 37 | */ 38 | 39 | 40 | public function push(packet:P2PPacket, incrNext:Boolean = false):void{ 41 | 42 | storage.push(packet); 43 | 44 | _restored && _group.addHaveObjects(storage.length,storage.length); 45 | 46 | incrNext && _next++; 47 | } 48 | 49 | /** 50 | * 51 | * Return next element to dispatch. 52 | * 53 | * @return 54 | */ 55 | 56 | 57 | public function next():P2PPacket{ 58 | 59 | if(!_restored || _next > storage.length - 1) return null; 60 | 61 | return storage[_next++]; 62 | } 63 | 64 | /** 65 | * 66 | * Add message from history. 67 | * 68 | * If index == 0 than data is expected history size. 69 | * 70 | * Updates restore value. 71 | * 72 | * @param index 73 | * @param data 74 | */ 75 | 76 | 77 | public function restore(index:Number, data:*):void{ 78 | 79 | if(index == 0){ 80 | _expectedSize = Number(data); 81 | _restored = (_expectedSize == 0); 82 | _restoreWaiting = !_restored; 83 | return; 84 | } 85 | 86 | temp.push(data as P2PPacket); 87 | 88 | !_restored && (restored = (_expectedSize == temp.length)); 89 | } 90 | 91 | 92 | /** 93 | * 94 | * Return message with index index-1 from mailbox or size of mailbox if index == 0. 95 | * 96 | * @param index 97 | * @return 98 | */ 99 | 100 | 101 | public function getMessage(index:Number):Object{ 102 | return index ? storage[index-1] : storage.length; 103 | } 104 | 105 | 106 | /** 107 | * 108 | * Flush all messages. 109 | * 110 | */ 111 | 112 | 113 | public function flush():void{ 114 | 115 | storage.length = 0; 116 | temp.length = 0; 117 | 118 | } 119 | 120 | /** 121 | * Return state as array of packets 122 | * 123 | */ 124 | 125 | public function get state():Array{ 126 | return temp; 127 | } 128 | 129 | 130 | /** 131 | * 132 | */ 133 | 134 | public function get size():Number{ 135 | return storage.length; 136 | } 137 | 138 | /** 139 | * 140 | * Define whether mailbox is in restoring state (i.e. collecting state data). 141 | * 142 | * If TRUE than all new messages are pushed to temp storage and have to fetched later. 143 | * 144 | */ 145 | 146 | public function get restored():Boolean { 147 | return _restored; 148 | } 149 | 150 | public function set restored(value:Boolean):void{ 151 | if(_restored == value) return; 152 | 153 | _restored = value; 154 | 155 | if(!value) return; 156 | 157 | _restoreWaiting && (_restoreWaiting = false); 158 | 159 | for each(var p:P2PPacket in temp) storage.unshift(p); 160 | 161 | _next = temp.length; 162 | 163 | } 164 | 165 | 166 | /** 167 | * 168 | * Indicates that we asked peers for data but haven't received anything yet (we don't know the size of a state array). 169 | * 170 | */ 171 | 172 | 173 | public function get restoreWaiting():Boolean { 174 | return _restoreWaiting; 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /examples/p2p_wb/src/wb.mxml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 106 | 107 | 111 | 112 | 113 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /examples/p2p_chat_starling/src/Chat.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.display.Loader; 4 | import flash.display.Sprite; 5 | import flash.display.StageAlign; 6 | import flash.display.StageOrientation; 7 | import flash.display.StageScaleMode; 8 | import flash.events.Event; 9 | import flash.filesystem.File; 10 | import flash.filesystem.FileMode; 11 | import flash.filesystem.FileStream; 12 | import flash.geom.Rectangle; 13 | import flash.system.Capabilities; 14 | import flash.utils.ByteArray; 15 | 16 | import starling.core.Starling; 17 | 18 | [SWF(width="960",height="640",frameRate="60",backgroundColor="#4a4137")] 19 | public class Chat extends Sprite 20 | { 21 | public function Chat() 22 | { 23 | if(this.stage) 24 | { 25 | this.stage.scaleMode = StageScaleMode.NO_SCALE; 26 | this.stage.align = StageAlign.TOP_LEFT; 27 | } 28 | this.mouseEnabled = this.mouseChildren = false; 29 | this.showLaunchImage(); 30 | this.loaderInfo.addEventListener(Event.COMPLETE, loaderInfo_completeHandler); 31 | } 32 | 33 | private var _starling:Starling; 34 | private var _launchImage:Loader; 35 | private var _savedAutoOrients:Boolean; 36 | 37 | private function showLaunchImage():void 38 | { 39 | var filePath:String; 40 | var isPortraitOnly:Boolean = false; 41 | if(Capabilities.manufacturer.indexOf("iOS") >= 0) 42 | { 43 | if(Capabilities.screenResolutionX == 1536 && Capabilities.screenResolutionY == 2048) 44 | { 45 | var isCurrentlyPortrait:Boolean = this.stage.orientation == StageOrientation.DEFAULT || this.stage.orientation == StageOrientation.UPSIDE_DOWN; 46 | filePath = isCurrentlyPortrait ? "Default-Portrait@2x.png" : "Default-Landscape@2x.png"; 47 | } 48 | else if(Capabilities.screenResolutionX == 768 && Capabilities.screenResolutionY == 1024) 49 | { 50 | isCurrentlyPortrait = this.stage.orientation == StageOrientation.DEFAULT || this.stage.orientation == StageOrientation.UPSIDE_DOWN; 51 | filePath = isCurrentlyPortrait ? "Default-Portrait.png" : "Default-Landscape.png"; 52 | } 53 | else if(Capabilities.screenResolutionX == 640) 54 | { 55 | isPortraitOnly = true; 56 | if(Capabilities.screenResolutionY == 1136) 57 | { 58 | filePath = "Default-568h@2x.png"; 59 | } 60 | else 61 | { 62 | filePath = "Default@2x.png"; 63 | } 64 | } 65 | else if(Capabilities.screenResolutionX == 320) 66 | { 67 | isPortraitOnly = true; 68 | filePath = "Default.png"; 69 | } 70 | } 71 | 72 | if(filePath) 73 | { 74 | var file:File = File.applicationDirectory.resolvePath(filePath); 75 | if(file.exists) 76 | { 77 | var bytes:ByteArray = new ByteArray(); 78 | var stream:FileStream = new FileStream(); 79 | stream.open(file, FileMode.READ); 80 | stream.readBytes(bytes, 0, stream.bytesAvailable); 81 | stream.close(); 82 | this._launchImage = new Loader(); 83 | this._launchImage.loadBytes(bytes); 84 | this.addChild(this._launchImage); 85 | this._savedAutoOrients = this.stage.autoOrients; 86 | this.stage.autoOrients = false; 87 | if(isPortraitOnly) 88 | { 89 | this.stage.setOrientation(StageOrientation.DEFAULT); 90 | } 91 | } 92 | } 93 | } 94 | 95 | private function loaderInfo_completeHandler(event:Event):void 96 | { 97 | Starling.handleLostContext = true; 98 | Starling.multitouchEnabled = true; 99 | this._starling = new Starling(ChatMain, this.stage); 100 | this._starling.enableErrorChecking = false; 101 | this._starling.start(); 102 | if(this._launchImage) 103 | { 104 | this._starling.addEventListener("rootCreated", starling_rootCreatedHandler); 105 | } 106 | 107 | this.stage.addEventListener(Event.RESIZE, stage_resizeHandler, false, int.MAX_VALUE, true); 108 | this.stage.addEventListener(Event.DEACTIVATE, stage_deactivateHandler, false, 0, true); 109 | } 110 | 111 | private function starling_rootCreatedHandler(event:Object):void 112 | { 113 | if(this._launchImage) 114 | { 115 | this.removeChild(this._launchImage); 116 | this._launchImage.unloadAndStop(true); 117 | this._launchImage = null; 118 | this.stage.autoOrients = this._savedAutoOrients; 119 | } 120 | } 121 | 122 | private function stage_resizeHandler(event:Event):void 123 | { 124 | this._starling.stage.stageWidth = this.stage.stageWidth; 125 | this._starling.stage.stageHeight = this.stage.stageHeight; 126 | 127 | const viewPort:Rectangle = this._starling.viewPort; 128 | viewPort.width = this.stage.stageWidth; 129 | viewPort.height = this.stage.stageHeight; 130 | try 131 | { 132 | this._starling.viewPort = viewPort; 133 | } 134 | catch(error:Error) {} 135 | } 136 | 137 | private function stage_deactivateHandler(event:Event):void 138 | { 139 | this._starling.stop(); 140 | this.stage.addEventListener(Event.ACTIVATE, stage_activateHandler, false, 0, true); 141 | } 142 | 143 | private function stage_activateHandler(event:Event):void 144 | { 145 | this.stage.removeEventListener(Event.ACTIVATE, stage_activateHandler); 146 | this._starling.start(); 147 | } 148 | 149 | } 150 | } -------------------------------------------------------------------------------- /examples/p2p_chat_starling/.actionScriptProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /examples/p2p_chat/src/chat.mxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -1 && _listProvider.removeItemAt(_listProvider.getItemIndex(_storage[e.info.peerID])); 62 | 63 | delete _storage[e.info.peerID]; 64 | 65 | if (_currentChat == e.info.peerID) switchTo("all"); 66 | 67 | } 68 | } 69 | 70 | private function onStateRestored(e:P2PEvent):void { 71 | 72 | restoreFromArray(e.info.state); 73 | 74 | p2pclient.receive = true; 75 | 76 | push_btn.enabled = true; 77 | 78 | switchTo("all"); 79 | 80 | p2pclient.send({label: _my_name, peerID: p2pclient.peerID}, "user"); 81 | 82 | } 83 | 84 | private function onConnect(e:P2PEvent):void { 85 | 86 | debug('P2P connection established'); 87 | 88 | _listProvider.addItem(_storage["all"]); 89 | 90 | } 91 | 92 | private function onFailure(e:P2PEvent):void { 93 | debug('P2P connection failed'); 94 | } 95 | 96 | 97 | private function userRegister(p:P2PPacket):void { 98 | 99 | if (!_storage[p.senderId]) { 100 | _storage[p.data.peerID] = {peerID: p.data.peerID, text: ""}; 101 | 102 | // we must ensure that user is really connected so waiting for PEER_CONNECT 103 | 104 | } else { 105 | 106 | _storage[p.senderId]["label"] = p.data.label; 107 | 108 | _listProvider.addItem(_storage[p.senderId]); 109 | } 110 | } 111 | 112 | private function messageReceived(p:P2PPacket):void { 113 | 114 | if (p.data.chat != "all" && !_storage[p.senderId]) return; 115 | 116 | const message:String = p.data.name + ": " + p.data.text + "\n"; 117 | 118 | if(p.data.chat == "all") _storage["all"]["text"] += message; 119 | else _storage[p.senderId]["text"] += message; 120 | 121 | if ( 122 | (p.senderId == _currentChat) || // private chat: compare sender with current 123 | (p.data.chat == _currentChat) // it is possible only if _currentChat == "all" 124 | ) 125 | textArea.text += message; 126 | 127 | } 128 | 129 | 130 | private function restoreFromArray(arr:Array):void { 131 | 132 | for each(var p:P2PPacket in arr) { 133 | if (p && p.type == "user") userRegister(p); 134 | else if (p && p.type == "message") messageReceived(p); 135 | } 136 | 137 | } 138 | 139 | 140 | private function switchTo(id:String):void { 141 | 142 | if (!_storage[id]) return; 143 | 144 | textArea.text = _storage[id]["text"]; 145 | 146 | title.text = _storage[id]['label']; 147 | 148 | _currentChat = id; 149 | 150 | } 151 | 152 | private function push():void { 153 | 154 | if (!inputField.text) return; 155 | 156 | p2pclient.send({chat: _currentChat, name: _my_name, text: inputField.text}, "message", _currentChat === "all", _currentChat === "all" ? "" : _currentChat); 157 | 158 | _storage[_currentChat]["text"] += _my_name + ": " + inputField.text + "\n"; 159 | 160 | textArea.text += _my_name + ": " + inputField.text + "\n"; 161 | 162 | inputField.text = ""; 163 | 164 | } 165 | 166 | 167 | private function listClick(e:Event):void { 168 | switchTo(users.selectedItem.peerID); 169 | } 170 | ]]> 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /examples/p2p_chat_starling/src/ChatMain.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | /** 4 | * @author seaders 5 | */ 6 | 7 | import com.greygreen.net.p2p.P2PClient; 8 | import com.greygreen.net.p2p.events.P2PEvent; 9 | import com.greygreen.net.p2p.model.P2PConfig; 10 | import com.greygreen.net.p2p.model.P2PPacket; 11 | 12 | import feathers.controls.Button; 13 | import feathers.controls.Label; 14 | import feathers.controls.List; 15 | import feathers.controls.ScrollContainer; 16 | import feathers.controls.TextArea; 17 | import feathers.controls.TextInput; 18 | import feathers.core.FeathersControl; 19 | import feathers.data.ListCollection; 20 | import feathers.events.FeathersEventType; 21 | import feathers.layout.HorizontalLayout; 22 | import feathers.layout.VerticalLayout; 23 | import feathers.themes.MetalWorksMobileTheme; 24 | 25 | import starling.display.DisplayObject; 26 | import starling.display.DisplayObjectContainer; 27 | import starling.display.Sprite; 28 | import starling.events.Event; 29 | 30 | public class ChatMain extends Sprite 31 | { 32 | private const p2pclient:P2PClient = new P2PClient(); 33 | 34 | /** 35 | * User to chat mapping (use peerID as a key or 'all' keyword for public chat) 36 | */ 37 | private var _storage:Object = { 38 | "all": {label: "all", peerID: "all", text: ""} 39 | } 40 | 41 | private var _currentChat:String = "all"; 42 | 43 | private var _my_name:String = "chatter-box-" + Math.floor(1000 * Math.random()); 44 | private var push_btn:Button; 45 | private var users:List; 46 | private var title:Label; 47 | private var inputField:TextInput; 48 | private var textArea:TextArea; 49 | 50 | private var _listProvider:ListCollection = new ListCollection(); 51 | 52 | private function debug(s:String):void 53 | { 54 | trace(s); 55 | } 56 | 57 | public function centralize( 58 | parent:DisplayObjectContainer, child:DisplayObject, 59 | centreH:Boolean=true, centreV:Boolean=false, 60 | centreByParent:Boolean=false 61 | ):void 62 | { 63 | parent.addChild(child); 64 | if(child is FeathersControl) 65 | { 66 | (child as FeathersControl).validate(); 67 | } 68 | if(!centreH && !centreV) 69 | { 70 | return; 71 | } 72 | 73 | var w:Number; 74 | var h:Number; 75 | if(!centreByParent) 76 | { 77 | w = this.stage.stageWidth; 78 | h = this.stage.stageHeight; 79 | } 80 | else 81 | { 82 | w = parent.width; 83 | h = parent.height; 84 | } 85 | 86 | if(centreH) 87 | { 88 | child.x = (w / 2) - (child.width / 2); 89 | } 90 | if(centreV) 91 | { 92 | child.y = (h / 2) - (child.height / 2); 93 | } 94 | } 95 | 96 | public function ChatMain() 97 | { 98 | //we'll initialize things after we've been added to the stage 99 | this.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); 100 | } 101 | 102 | /** 103 | * The Feathers Button control that we'll be creating. 104 | */ 105 | protected var button:Button; 106 | 107 | /** 108 | * Where the magic happens. Start after the main class has been added 109 | * to the stage so that we can access the stage property. 110 | */ 111 | protected function addedToStageHandler(event:Event):void 112 | { 113 | this.removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler); 114 | new MetalWorksMobileTheme(); 115 | 116 | users = new List(); 117 | addChild(users); 118 | users.validate(); 119 | 120 | users.isSelectable = true; 121 | users.dataProvider = _listProvider; 122 | 123 | users.width = 150; 124 | users.height = this.stage.stageHeight; 125 | users.addEventListener(Event.CHANGE, listClick); 126 | 127 | const vlayout:VerticalLayout = new VerticalLayout(); 128 | 129 | var vgroup:ScrollContainer = new ScrollContainer(); 130 | vgroup.layout = vlayout; 131 | 132 | addChild(vgroup); 133 | vgroup.x = 155; 134 | vgroup.width = this.stage.stageWidth - vgroup.x; 135 | vgroup.height = this.stage.stageHeight; 136 | 137 | title = new Label(); 138 | title.text = ' '; 139 | vgroup.addChild(title); 140 | title.validate(); 141 | title.width = vgroup.width; 142 | title.height = 30; 143 | 144 | textArea = new TextArea(); 145 | vgroup.addChild(textArea); 146 | textArea.validate(); 147 | textArea.width = vgroup.width; 148 | 149 | const hlayout:HorizontalLayout = new HorizontalLayout(); 150 | 151 | var hgroup:ScrollContainer = new ScrollContainer(); 152 | hgroup.layout = hlayout; 153 | vgroup.addChild(hgroup); 154 | hgroup.width = vgroup.width; 155 | 156 | inputField = new TextInput(); 157 | hgroup.addChild(inputField); 158 | inputField.validate(); 159 | inputField.text = 'sample'; 160 | inputField.addEventListener(FeathersEventType.ENTER, push); 161 | 162 | push_btn = new Button(); 163 | hgroup.addChild(push_btn); 164 | push_btn.validate(); 165 | push_btn.horizontalAlign = Button.HORIZONTAL_ALIGN_CENTER; 166 | push_btn.label = 'Push'; 167 | 168 | inputField.width = hgroup.width - push_btn.width - 10; 169 | 170 | hgroup.validate(); 171 | 172 | textArea.height = vgroup.height - (title.height + hgroup.height); 173 | 174 | push_btn.addEventListener(Event.TRIGGERED, push); 175 | 176 | setupConnection(); 177 | } 178 | 179 | private function setupConnection():void { 180 | 181 | p2pclient.addEventListener(P2PEvent.FAILED, onFailure); 182 | p2pclient.addEventListener(P2PEvent.CONNECTED, onConnect); 183 | p2pclient.addEventListener(P2PEvent.STATE_RESTORED, onStateRestored); 184 | p2pclient.addEventListener(P2PEvent.STATE_RESTORE_FAILED, onFailure); 185 | p2pclient.addEventListener(P2PEvent.PEER_CONNECTED, userJoin); 186 | p2pclient.addEventListener(P2PEvent.PEER_DISCONNECTED, userLeave); 187 | 188 | p2pclient.listen(messageReceived, "message"); 189 | 190 | p2pclient.listen(userRegister, "user"); 191 | 192 | p2pclient.connect(new P2PConfig({ 193 | groupName: "p2p/chat/1", 194 | saveState: "true" 195 | })); 196 | 197 | } 198 | 199 | private function userJoin(e:P2PEvent):void { 200 | if (!_storage[e.info.peerID]) { // we have't received user registration 201 | _storage[e.info.peerID] = {peerID: e.info.peerID, text: ""}; 202 | } else { // we've already received user info (obviously on restore state); than add to list 203 | _listProvider.addItem(_storage[e.info.peerID]); 204 | } 205 | } 206 | 207 | private function userLeave(e:P2PEvent):void { 208 | if (_storage[e.info.peerID]) { 209 | 210 | _listProvider.getItemIndex(_storage[e.info.peerID]) > -1 && _listProvider.removeItemAt(_listProvider.getItemIndex(_storage[e.info.peerID])); 211 | 212 | delete _storage[e.info.peerID]; 213 | 214 | if (_currentChat == e.info.peerID) switchTo("all"); 215 | 216 | } 217 | } 218 | 219 | private function onStateRestored(e:P2PEvent):void { 220 | 221 | restoreFromArray(e.info.state); 222 | 223 | p2pclient.receive = true; 224 | 225 | push_btn.isEnabled = true; 226 | 227 | switchTo("all"); 228 | 229 | p2pclient.send({label: _my_name, peerID: p2pclient.peerID}, "user"); 230 | 231 | } 232 | 233 | private function onConnect(e:P2PEvent):void { 234 | 235 | debug('P2P connection established'); 236 | 237 | _listProvider.addItem(_storage["all"]); 238 | 239 | } 240 | 241 | private function onFailure(e:P2PEvent):void { 242 | debug('P2P connection failed'); 243 | } 244 | 245 | 246 | private function userRegister(p:P2PPacket):void { 247 | 248 | if (!_storage[p.senderId]) { 249 | _storage[p.data.peerID] = {peerID: p.data.peerID, text: ""}; 250 | 251 | // we must ensure that user is really connected so waiting for PEER_CONNECT 252 | 253 | } else { 254 | 255 | _storage[p.senderId]["label"] = p.data.label; 256 | 257 | _listProvider.addItem(_storage[p.senderId]); 258 | } 259 | } 260 | 261 | private function messageReceived(p:P2PPacket):void { 262 | 263 | if (p.data.chat != "all" && !_storage[p.senderId]) return; 264 | 265 | const message:String = p.data.name + ": " + p.data.text + "\n"; 266 | 267 | if(p.data.chat == "all") _storage["all"]["text"] += message; 268 | else _storage[p.senderId]["text"] += message; 269 | 270 | const valid:Boolean = (p.senderId == _currentChat) || // private chat: compare sender with current 271 | (p.data.chat == _currentChat) || // it is possible only if _currentChat == "all" 272 | (p.data.chat == p2pclient.peerID); 273 | if (valid) 274 | textArea.text += message; 275 | 276 | trace(valid, message, p.senderId, p.data.chat); 277 | p = null; 278 | } 279 | 280 | 281 | private function restoreFromArray(arr:Array):void { 282 | 283 | for each(var p:P2PPacket in arr) { 284 | if (p && p.type == "user") userRegister(p); 285 | else if (p && p.type == "message") messageReceived(p); 286 | } 287 | 288 | } 289 | 290 | 291 | private function switchTo(id:String):void { 292 | 293 | if (!_storage[id]) return; 294 | 295 | textArea.text = _storage[id]["text"]; 296 | 297 | title.text = _storage[id]['label']; 298 | 299 | _currentChat = id; 300 | 301 | } 302 | 303 | private function push():void { 304 | 305 | if (!inputField.text) return; 306 | 307 | p2pclient.send({chat: _currentChat, name: _my_name, text: inputField.text}, "message", _currentChat === "all", _currentChat === "all" ? "" : _currentChat); 308 | 309 | _storage[_currentChat]["text"] += _my_name + ": " + inputField.text + "\n"; 310 | 311 | textArea.text += _my_name + ": " + inputField.text + "\n"; 312 | 313 | inputField.text = ""; 314 | 315 | } 316 | 317 | 318 | private function listClick():void { 319 | if(null != users.selectedItem) 320 | { 321 | switchTo(users.selectedItem.peerID); 322 | } 323 | } 324 | } 325 | } 326 | 327 | -------------------------------------------------------------------------------- /examples/p2p_chat_starling/src/Chat-app.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 18 | StarlingFeathersTest 19 | 20 | 21 | StarlingFeathersTest 22 | 23 | 25 | StarlingFeathersTest 26 | 27 | 30 | 0.0.0 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | [This value will be overwritten by Flash Builder in the output app.xml] 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | direct 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | true 115 | true 116 | true 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 138 | 157 | 158 | 161 | 162 | 163 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 212 | 213 | 222 | 223 | 224 | 225 | 226 | 229 | 230 | 231 | 232 | 233 | 234 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 262 | 263 | 265 | 266 | 267 | 268 | 269 | 271 | 272 | 273 | 274 | 275 | 277 | 278 | 279 | 280 | 281 | ]]> 282 | 283 | 284 | UIDeviceFamily 286 | 287 | 1 288 | 2 289 | 290 | ]]> 291 | high 292 | 293 | 294 | -------------------------------------------------------------------------------- /src/com/greygreen/net/p2p/P2PClient.as: -------------------------------------------------------------------------------- 1 | /** 2 | * User: palkan 3 | * Date: 8/20/13 4 | * Time: 2:05 PM 5 | */ 6 | package com.greygreen.net.p2p { 7 | import com.greygreen.net.p2p.events.P2PEvent; 8 | import com.greygreen.net.p2p.model.P2PConfig; 9 | import com.greygreen.net.p2p.model.P2PPacket; 10 | 11 | import flash.errors.IllegalOperationError; 12 | import flash.events.EventDispatcher; 13 | import flash.events.NetStatusEvent; 14 | import flash.net.GroupSpecifier; 15 | import flash.net.NetConnection; 16 | import flash.net.NetGroup; 17 | import flash.net.NetGroupInfo; 18 | import flash.utils.setTimeout; 19 | 20 | import ru.teachbase.constants.NetConnectionStatusCodes; 21 | import ru.teachbase.constants.NetGroupStatusCodes; 22 | import ru.teachbase.utils.extensions.FuncObject; 23 | 24 | /** 25 | * Dispatches when NetConnection and NetGroup successfully connected. 26 | * 27 | * @eventType com.greygreen.net.p2p.events.P2PEvent.CONNECTED 28 | */ 29 | 30 | [Event(type="com.greygreen.net.p2p.events.P2PEvent", name="p2p:connected")] 31 | 32 | /** 33 | * Dispatches when session state is restored. 34 | * 35 | * @eventType com.greygreen.net.p2p.events.P2PEvent.STATE_RESTORED 36 | */ 37 | 38 | [Event(type="com.greygreen.net.p2p.events.P2PEvent", name="p2p:state_restored")] 39 | 40 | /** 41 | * Dispatches when session state restore proccess is failed. 42 | * 43 | * @eventType com.greygreen.net.p2p.events.P2PEvent.STATE_RESTORE_FAILED 44 | */ 45 | 46 | [Event(type="com.greygreen.net.p2p.events.P2PEvent", name="p2p:state_restore_failed")] 47 | 48 | /** 49 | * Dispatches when some error occurred. 50 | * 51 | * @eventType com.greygreen.net.p2p.events.P2PEvent.FAILED 52 | */ 53 | 54 | [Event(type="com.greygreen.net.p2p.events.P2PEvent", name="p2p:failed")] 55 | 56 | /** 57 | * Dispatches when NetConnection closed successfully (e.g. connection closing was intended). 58 | * 59 | * @eventType com.greygreen.net.p2p.events.P2PEvent.CLOSED 60 | */ 61 | 62 | [Event(type="com.greygreen.net.p2p.events.P2PEvent", name="p2p:closed")] 63 | 64 | /** 65 | * Dispatches when new peer (actually, neighbor) connected to NetGroup. 66 | * 67 | * @eventType com.greygreen.net.p2p.events.P2PEvent.PEER_CONNECTED 68 | */ 69 | 70 | [Event(type="com.greygreen.net.p2p.events.P2PEvent", name="p2p:peer_connected")] 71 | 72 | /** 73 | * Dispatches when peer disconnected. 74 | * 75 | * @eventType com.greygreen.net.p2p.events.P2PEvent.PEER_DISCONNECTED 76 | */ 77 | 78 | [Event(type="com.greygreen.net.p2p.events.P2PEvent", name="p2p:peer_disconnected")] 79 | 80 | 81 | 82 | /** 83 | * 84 | * P2PClient works with serverless RTMFP connections and handle such tasks as connecting to group, sending/receiving messages, data replication. 85 | * 86 | * It is possible to store all session messages in mailbox as replicated object (kinda history storage). Mailbox can be filled up with external data (e.g. you can store data somewhere else for backup). 87 | * 88 | * Messages are divided into two classes: system and temporary. Only system messages are stored in the mailbox. 89 | * 90 | * Every message also has a type (string identifier) which is used for separated message dispatching. 91 | * 92 | */ 93 | 94 | public class P2PClient extends EventDispatcher{ 95 | 96 | private const RESTORE_MAX_TRIES:int = 3; 97 | 98 | private const connection:NetConnection = new NetConnection(); 99 | private const listeners:FuncObject = new FuncObject(); 100 | 101 | private var _mailbox:P2PMailbox; 102 | private var _group:NetGroup; 103 | 104 | private var _replicateState:Boolean = true; 105 | private var _restoreTries:int = 0; 106 | 107 | private var _config:P2PConfig; 108 | 109 | private var _receive:Boolean = false; 110 | 111 | private var _connected:Boolean = false; 112 | private var _disposed:Boolean = false; 113 | 114 | private var _neighborHasConnected:Boolean = false; 115 | 116 | /** 117 | * 118 | * Creates new P2PClient instance. 119 | */ 120 | 121 | public function P2PClient(){ 122 | new P2PPacket(); 123 | } 124 | 125 | 126 | /** 127 | * 128 | * Creates new local RTMFP Connection. 129 | * 130 | * @param config 131 | * 132 | * @throws IllegalOperationError If client is already connected. 133 | */ 134 | 135 | public function connect(config:P2PConfig):void{ 136 | 137 | if(_disposed) return; 138 | 139 | if(_connected) throw new IllegalOperationError("P2PClient is already connected"); 140 | 141 | _config = config; 142 | 143 | _replicateState = _config.saveState; 144 | 145 | _receive = !_config.saveState; 146 | 147 | connection.addEventListener(NetStatusEvent.NET_STATUS, connectionStatus); 148 | 149 | connection.connect("rtmfp:"); 150 | 151 | } 152 | 153 | /** 154 | * 155 | * When recipient is provided uses sendToNearest() to send message. 156 | * Otherwise uses post(). 157 | * 158 | * Sending is disabled if on of the following: 159 | * 160 | *
  • - NetConnection or NetGroup is not connected;
  • 161 | *
  • - mailbox is enabled but not restored;
  • 162 | *
  • - receive == false;
  • 163 | * 164 | * @param data 165 | * @param type 166 | * @param system 167 | * @param recipient Recipient peerID or empty string 168 | */ 169 | 170 | public function send(data:*, type:String = "default", system:Boolean = true, recipient:String = ""):void{ 171 | 172 | if(!_connected || !_receive || (_mailbox && !_mailbox.restored)) return; 173 | 174 | const packet:P2PPacket = new P2PPacket(type, data, connection.nearID, recipient, !recipient && system); 175 | 176 | if(!recipient){ 177 | _group.post(packet); 178 | _mailbox && system && _mailbox.push(packet,true); 179 | } 180 | else _group.sendToNearest(packet,_group.convertPeerIDToGroupAddress(recipient)); 181 | 182 | } 183 | 184 | 185 | /** 186 | * 187 | * Register new message listener. 188 | * 189 | * Listener must be a Function accepting one argument of a type P2PPacket. 190 | * 191 | * It is possible to register arbitrary number of listeners of one type. 192 | * 193 | * @param type 194 | * @param handler 195 | * 196 | * @see P2PPacket$ 197 | */ 198 | 199 | public function listen(handler:Function,type:String = "default"):void{ 200 | if(_disposed) return; 201 | (handler is Function) && (listeners[type] = handler); 202 | } 203 | 204 | 205 | 206 | /** 207 | * 208 | * Unregister listener 209 | * 210 | * @param type 211 | * @param handler 212 | */ 213 | 214 | 215 | public function unlisten(type:String, handler:Function):void{ 216 | if(_disposed) return; 217 | listeners.deleteFromProperty(type,handler); 218 | } 219 | 220 | 221 | 222 | 223 | /** 224 | */ 225 | 226 | 227 | protected function setupGroup():void{ 228 | 229 | var groupspec:GroupSpecifier = new GroupSpecifier(_config.groupName); 230 | groupspec.postingEnabled = true; 231 | groupspec.routingEnabled = true; 232 | groupspec.ipMulticastMemberUpdatesEnabled = true; 233 | groupspec.addIPMulticastAddress(_config.ip); 234 | groupspec.objectReplicationEnabled = _config.saveState; 235 | 236 | _group = new NetGroup(connection,groupspec.groupspecWithAuthorizations()); 237 | 238 | if(_config.saveState) 239 | _mailbox = new P2PMailbox(_group); 240 | 241 | _group.addEventListener(NetStatusEvent.NET_STATUS,connectionStatus); 242 | } 243 | 244 | /** 245 | * 246 | * @param packet 247 | */ 248 | 249 | private function sendPacket(packet:P2PPacket):void{ 250 | 251 | if(!packet) return; 252 | 253 | (listeners[packet.type] is Function) && listeners[packet.type].call(null,packet); 254 | } 255 | 256 | /** 257 | * 258 | * @param packet 259 | */ 260 | 261 | private function handlePacket(packet:P2PPacket):void{ 262 | 263 | if(!packet) return; 264 | 265 | _mailbox && packet.system && !_mailbox.restoreWaiting && _mailbox.push(packet); 266 | 267 | if(!_receive) return; 268 | 269 | var packetToSend:P2PPacket; 270 | 271 | if(!_mailbox || (_mailbox && !packet.system && _mailbox.restored)) 272 | packetToSend = packet; 273 | else if(_mailbox.restored) 274 | packetToSend = _mailbox.next(); 275 | 276 | sendPacket(packetToSend); 277 | } 278 | 279 | /** 280 | * 281 | * @param index 282 | */ 283 | 284 | private function restoreState(index:Number = 0):void{ 285 | debug("[restore] size of group: "+_group.neighborCount); 286 | if(!_group.neighborCount){ 287 | _mailbox.restored = true; 288 | } 289 | 290 | if(_mailbox.restored){ 291 | dispatchEvent(new P2PEvent(P2PEvent.STATE_RESTORED,{state:_mailbox.state})); 292 | return; 293 | } 294 | 295 | if(_neighborHasConnected) _group.addWantObjects(index,index); 296 | else setTimeout(restoreState,100); 297 | } 298 | 299 | 300 | 301 | private function dispose():void{ 302 | 303 | connection.removeEventListener(NetStatusEvent.NET_STATUS, connectionStatus); 304 | _group.removeEventListener(NetStatusEvent.NET_STATUS, connectionStatus); 305 | 306 | listeners.dispose(); 307 | _mailbox && _mailbox.flush(); 308 | 309 | _disposed = true; 310 | } 311 | 312 | /** 313 | * 314 | * @param e 315 | */ 316 | 317 | protected function connectionStatus(e:NetStatusEvent):void{ 318 | 319 | debug('P2P net status: '+ e.info.code); 320 | 321 | switch(e.info.code){ 322 | // group events 323 | case NetGroupStatusCodes.POST_MESSAGE:{ 324 | var packet:P2PPacket = e.info.message as P2PPacket; 325 | handlePacket(packet); 326 | break; 327 | } 328 | case NetGroupStatusCodes.MESSAGE_SENT_TO:{ 329 | var packet:P2PPacket = e.info.message as P2PPacket; 330 | if(e.info.fromLocal == true){ 331 | handlePacket(packet); 332 | }else{ 333 | _group.sendToNearest(packet,_group.convertPeerIDToGroupAddress(packet.recipientId)); 334 | } 335 | break; 336 | } 337 | case NetGroupStatusCodes.NEIGHBOR_CONNECT:{ 338 | _neighborHasConnected = true; 339 | dispatchEvent(new P2PEvent(P2PEvent.PEER_CONNECTED,e.info)); 340 | _mailbox && _mailbox.restored && _group.addHaveObjects(0,_mailbox.size); 341 | break; 342 | } 343 | case NetGroupStatusCodes.NEIGHBOR_DISCONNECT:{ 344 | dispatchEvent(new P2PEvent(P2PEvent.PEER_DISCONNECTED,e.info)); 345 | break; 346 | } 347 | case NetGroupStatusCodes.REPLICATION_REQUEST:{ 348 | if(e.info.index <= _mailbox.size) _group.writeRequestedObject(e.info.requestID, _mailbox.getMessage(e.info.index)); 349 | else _group.denyRequestedObject(e.info.requestID); 350 | break; 351 | } 352 | case NetGroupStatusCodes.REPLICATION_DATA:{ 353 | _restoreTries = 0; 354 | _mailbox.restore(e.info.index, e.info.object); 355 | restoreState(e.info.index+1); 356 | break; 357 | } 358 | case NetGroupStatusCodes.REPLICATION_FAILED:{ 359 | if(_restoreTries === RESTORE_MAX_TRIES){ 360 | _group.removeWantObjects(e.info.index, e.info.index); 361 | dispatchEvent(new P2PEvent(P2PEvent.STATE_RESTORE_FAILED)); 362 | }else{ 363 | restoreState(e.info.index); 364 | _restoreTries++; 365 | } 366 | break; 367 | } 368 | case NetGroupStatusCodes.REPLICATION_SEND:{ 369 | debug("Index: "+ e.info.index); 370 | break; 371 | } 372 | case NetGroupStatusCodes.CONNECTED:{ 373 | _connected = true; 374 | if(_replicateState) setTimeout(restoreState,100); 375 | dispatchEvent(new P2PEvent(P2PEvent.CONNECTED, {group:_group})); 376 | break; 377 | } 378 | case NetGroupStatusCodes.REJECTED: 379 | case NetGroupStatusCodes.FAILED:{ 380 | if(_connected) 381 | dispatchEvent(new P2PEvent(P2PEvent.FAILED,{message: e.info.code})); 382 | 383 | dispose(); 384 | break; 385 | } 386 | 387 | //net connection events 388 | case NetConnectionStatusCodes.SUCCESS: 389 | setupGroup(); 390 | break; 391 | case NetConnectionStatusCodes.FAILED: 392 | case NetConnectionStatusCodes.CLOSED:{ 393 | if(_connected) 394 | dispatchEvent(new P2PEvent(P2PEvent.FAILED,{message: e.info.code})); 395 | else if(!_disposed) 396 | dispatchEvent(new P2PEvent(P2PEvent.CLOSED)); 397 | 398 | dispose(); 399 | break; 400 | } 401 | 402 | } 403 | 404 | } 405 | 406 | /** 407 | * Returns NetGroup info or null if is not connected. 408 | * 409 | */ 410 | 411 | public function get info():NetGroupInfo{ 412 | 413 | return _connected ? _group.info : null; 414 | 415 | } 416 | 417 | /** 418 | * 419 | */ 420 | 421 | public function get connected():Boolean{ 422 | 423 | return _connected; 424 | 425 | } 426 | 427 | 428 | /** 429 | * 430 | * Define whether to receive incoming messages. 431 | * 432 | * While using mailbox all incoming messages are stored and dispatched on setting this to TRUE. 433 | * 434 | */ 435 | 436 | public function get receive():Boolean { 437 | return _receive; 438 | } 439 | 440 | public function set receive(value:Boolean):void { 441 | _receive = value; 442 | 443 | if(value && _mailbox){ 444 | 445 | var p:P2PPacket; 446 | while(p = _mailbox.next()) sendPacket(p); 447 | 448 | } 449 | 450 | } 451 | 452 | /** 453 | * Return my own peerID 454 | */ 455 | 456 | public function get peerID():String{ 457 | return _connected ? connection.nearID : ""; 458 | } 459 | 460 | private function debug(s:String):void 461 | { 462 | // import ru.teachbase.utils.shortcuts.debug; 463 | trace(s); 464 | } 465 | 466 | } 467 | } 468 | --------------------------------------------------------------------------------