├── Server ├── run.bat ├── .dotcloudignore ├── npm-install.bat ├── dotcloud.yml ├── .dotcloud │ └── config ├── package.json ├── crossdomain.xml ├── app.js └── index.html ├── Extension ├── include.xml └── com │ └── gemioli │ └── io │ ├── utils │ ├── Int32.hx │ ├── Utils.hx │ ├── BaseCode64.hx │ └── URLParser.hx │ ├── net │ ├── WebSocket.js │ ├── events │ │ ├── ErrorEvent.hx │ │ ├── MessageEvent.hx │ │ └── CloseEvent.hx │ └── WebSocket.hx │ ├── SocketConnectionStatus.hx │ ├── events │ ├── TransportEvent.hx │ ├── SocketProxyEvent.hx │ └── SocketEvent.hx │ ├── transports │ ├── WebSocketTransport.hx │ └── XHRPollingTransport.hx │ ├── Transport.hx │ ├── SocketProxy.hx │ └── Socket.hx ├── Project ├── Extension Test.xml ├── Assets │ └── nme.svg ├── Extension Test.hxproj └── Source │ └── com │ └── gemioli │ └── ExtensionTest.hx └── README.md /Server/run.bat: -------------------------------------------------------------------------------- 1 | node app.js -------------------------------------------------------------------------------- /Server/.dotcloudignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-install.bat 3 | run.bat -------------------------------------------------------------------------------- /Extension/include.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Server/npm-install.bat: -------------------------------------------------------------------------------- 1 | ::npm-install.bat 2 | @echo off 3 | ::install server dependencies 4 | npm install -d -------------------------------------------------------------------------------- /Server/dotcloud.yml: -------------------------------------------------------------------------------- 1 | www: 2 | type: nodejs 3 | processes: 4 | app: node app.js 5 | config: 6 | node_version: v0.8.x 7 | -------------------------------------------------------------------------------- /Server/.dotcloud/config: -------------------------------------------------------------------------------- 1 | { 2 | "push_branch": null, 3 | "application": "socketioserver", 4 | "version": "0.9.4", 5 | "push_protocol": "rsync" 6 | } -------------------------------------------------------------------------------- /Server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"socket.io-nme-server", 3 | "version":"0.0.1", 4 | "private":false, 5 | "dependencies":{ 6 | "socket.io": ">=0.9.4" 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Server/crossdomain.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Extension/com/gemioli/io/utils/Int32.hx: -------------------------------------------------------------------------------- 1 | package com.gemioli.io.utils; 2 | 3 | #if haxe3 4 | class Int32 { 5 | 6 | var val : Int; 7 | 8 | function new(val : Int) { 9 | this.val = val; 10 | } 11 | 12 | public static inline function ofInt(i : Int) : Int32 { 13 | return new Int32(i); 14 | } 15 | 16 | public static inline function add(a : Int32, b : Int32) : Int32 { 17 | return new Int32(a.val + b.val); 18 | } 19 | 20 | public static inline function toInt(a : Int32) : Int { 21 | return a.val; 22 | } 23 | 24 | } 25 | #end -------------------------------------------------------------------------------- /Extension/com/gemioli/io/net/WebSocket.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | if (window.WebSocket) { 4 | return; 5 | } else if (window.MozWebSocket) { 6 | window.WebSocket = MozWebSocket; 7 | return; 8 | } 9 | window.WebSocket = function(url) { 10 | var self = this; 11 | self.__init = setTimeout(function() { 12 | self.close(); 13 | }, 0); 14 | }; 15 | 16 | WebSocket.prototype.send = function(data) { 17 | }; 18 | 19 | WebSocket.prototype.close = function() { 20 | if (this.onclose != null) 21 | this.onclose(); 22 | }; 23 | 24 | })(); 25 | -------------------------------------------------------------------------------- /Project/Extension Test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Project/Assets/nme.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Extension/com/gemioli/io/SocketConnectionStatus.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io; 24 | 25 | enum SocketConnectionStatus 26 | { 27 | DISCONNECTED; 28 | CONNECTED; 29 | DISCONNECTING; 30 | CONNECTING; 31 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/utils/Utils.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.utils; 24 | 25 | import haxe.Utf8; 26 | 27 | class Utils 28 | { 29 | public static function Utf8Substr(str : String, pos : Int, len : Int) : String 30 | { 31 | if (len == 0) 32 | return ""; 33 | // TODO: This is bug in CPP version of Haxe.Utf8 - [len] param is calculated from 0 position 34 | #if cpp 35 | return Utf8.sub(str, pos, len + pos); 36 | #else 37 | return Utf8.sub(str, pos, len); 38 | #end 39 | } 40 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/net/events/ErrorEvent.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.net.events; 24 | 25 | #if openfl 26 | import flash.events.Event; 27 | #else 28 | import nme.events.Event; 29 | #end 30 | 31 | class ErrorEvent extends Event 32 | { 33 | public inline static var ERROR : String = "error"; 34 | 35 | public var message(default, null) : String; 36 | 37 | public function new(message : String) 38 | { 39 | super(ERROR, false, false); 40 | 41 | this.message = message; 42 | } 43 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/net/events/MessageEvent.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.net.events; 24 | 25 | #if openfl 26 | import flash.events.Event; 27 | #else 28 | import nme.events.Event; 29 | #end 30 | 31 | class MessageEvent extends Event 32 | { 33 | public inline static var MESSAGE : String = "message"; 34 | 35 | public var data(default, null) : Dynamic; 36 | 37 | public function new(data : Dynamic) 38 | { 39 | super(MESSAGE, false, false); 40 | 41 | this.data = data; 42 | } 43 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/events/TransportEvent.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.events; 24 | #if openfl 25 | import flash.events.Event; 26 | #else 27 | import nme.events.Event; 28 | #end 29 | 30 | 31 | class TransportEvent extends Event 32 | { 33 | public static var OPENED : String = "opened"; 34 | public static var CLOSED : String = "closed"; 35 | public static var MESSAGE : String = "message"; 36 | 37 | public var message : String; 38 | 39 | public function new(type : String, bubbles : Bool = false, cancelable : Bool = false, message : String = "") 40 | { 41 | super(type, bubbles, cancelable); 42 | this.message = message; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/events/SocketProxyEvent.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.events; 24 | #if openfl 25 | import flash.events.Event; 26 | #else 27 | import nme.events.Event; 28 | #end 29 | 30 | 31 | class SocketProxyEvent extends Event 32 | { 33 | public var id(default, null) : Int; // Message type in spec 34 | public var ack(default, null) : String; // Converted to Int in socket 35 | public var data(default, null) : String; 36 | 37 | public function new(endpoint : String, id : Int, ack : String, data : String, bubbles : Bool = false, cancelable : Bool = false) 38 | { 39 | super(endpoint, bubbles, cancelable); 40 | this.id = id; 41 | this.ack = ack; 42 | this.data = data; 43 | 44 | } 45 | } -------------------------------------------------------------------------------- /Project/Extension Test.hxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Server/app.js: -------------------------------------------------------------------------------- 1 | var app = require('http').createServer(handler) 2 | , io = require('socket.io').listen(app) 3 | , fs = require('fs') 4 | 5 | io.set('flash policy port', -1) 6 | 7 | io.set('transports', [ 8 | 'websocket' 9 | , 'flashsocket' 10 | , 'htmlfile' 11 | , 'xhr-polling' 12 | , 'jsonp-polling' 13 | ]); 14 | 15 | app.listen(8080); 16 | 17 | function handler (req, res) { 18 | if (req.url == '/crossdomain.xml') 19 | { 20 | fs.readFile(__dirname + '/crossdomain.xml', 21 | function (err, data) { 22 | if (err) { 23 | res.writeHead(500); 24 | return res.end('Error loading cd.xml'); 25 | } 26 | res.writeHead(200, {'Content-Type': 'text/plain'}); 27 | res.end(data); 28 | }); 29 | return; 30 | } 31 | fs.readFile(__dirname + '/index.html', 32 | function (err, data) { 33 | if (err) { 34 | res.writeHead(500); 35 | return res.end('Error loading index.html'); 36 | } 37 | res.writeHead(200); 38 | res.end(data); 39 | }); 40 | } 41 | 42 | io.sockets.on('connection', function (socket) { 43 | socket.send('Hello'); 44 | socket.on('message', function(msg) { 45 | console.log("Client say [" + msg + "]"); 46 | }); 47 | socket.emit('ServerEvent', {name : 'Jerry'}); 48 | socket.on('ClientEventEmpty', function () { 49 | console.log('ClientEventEmpty'); 50 | }); 51 | socket.on('ClientEventData', function (data) { 52 | console.log('ClientEventData [' + data.myData + ']'); 53 | }); 54 | socket.on('ClientEventCallback', function (fn) { 55 | console.log('ClientEventCallback'); 56 | fn('Done'); 57 | }); 58 | socket.on('Ping', function (data) { 59 | console.log('Ping packet ' + data); 60 | socket.emit('Pong', data); 61 | }); 62 | }); 63 | 64 | var chat = io 65 | .of('/chat') 66 | .on('connection', function(socket) { 67 | socket.on('message', function (msg) { 68 | console.log('New message to chat [' + msg + ']'); 69 | }); 70 | socket.send('hi from chat'); 71 | }); -------------------------------------------------------------------------------- /Server/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | socket.io-openfl-client 2 | ==================== 3 | 4 | Socket.io OpenFL client extension. 5 | 6 | ###What's done: 7 | * Interface of socket.io client (v.9) (http://socket.io/#how-to-use) 8 | * Multiplexing of sockets 9 | * Automatic reconnection of sockets 10 | * WebSocket and xhr-polling transports 11 | * Socket options: transports, reconnect, reconnectionAttempts, reconnectionDelay, connectTimeout, flashPolicyPort, flashPolicyUrl 12 | 13 | ###ToDo: 14 | * Secure connections 15 | * Optimizations 16 | 17 | ###Tested with HaXe 3.x, OpenFL 1.3.0, nodejs 0.10.26 (or dotcloud) on platforms: 18 | * Flash 11 19 | * HTML5 20 | * Windows 21 | * Android 22 | * iOS 23 | * Blackberry (not tested) 24 | 25 | ###Folders: 26 | * Extension - extension code 27 | * Project - example project files 28 | * Server - simple nodeJS server code (run npm-install.bat, then run.bat) 29 | 30 | ###Example: 31 | See [example](https://github.com/dimanux/socket.io-openfl-client/blob/master/Project/Source/com/gemioli/ExtensionTest.hx) 32 | 33 | ###License: 34 | 35 | (The MIT License) 36 | 37 | Copyright (c) Dmitriy Kapustin (dimanux), gemioli.com 38 | 39 | Permission is hereby granted, free of charge, to any person obtaining a copy 40 | of this software and associated documentation files (the "Software"), to deal 41 | in the Software without restriction, including without limitation the rights 42 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 43 | copies of the Software, and to permit persons to whom the Software is 44 | furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included in 47 | all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 50 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 51 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 52 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 53 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 54 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 55 | THE SOFTWARE. 56 | -------------------------------------------------------------------------------- /Extension/com/gemioli/io/events/SocketEvent.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.events; 24 | #if openfl 25 | import flash.events.Event; 26 | #else 27 | import nme.events.Event; 28 | #end 29 | 30 | 31 | class SocketEvent extends Event 32 | { 33 | public static var CONNECT : String = "connect"; 34 | public static var CONNECTING : String = "connecting"; 35 | public static var DISCONNECT : String = "disconnect"; 36 | public static var DISCONNECTING : String = "disconnecting"; 37 | public static var CONNECT_FAILED : String = "connect_failed"; 38 | public static var ERROR : String = "error"; 39 | public static var MESSAGE : String = "message"; 40 | public static var RECONNECT_FAILED : String = "reconnect_failed"; 41 | public static var RECONNECT : String = "reconnect"; 42 | public static var RECONNECTING : String = "reconnecting"; 43 | 44 | public var args : Dynamic; 45 | 46 | public function new(type : String, args : Dynamic = null) 47 | { 48 | super(type, false, false); 49 | 50 | this.args = args; 51 | } 52 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/net/events/CloseEvent.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.net.events; 24 | 25 | #if openfl 26 | import flash.events.Event; 27 | #else 28 | import nme.events.Event; 29 | #end 30 | 31 | class CloseEvent extends Event 32 | { 33 | public inline static var CLOSE_NORMAL : Int = 1000; 34 | public inline static var CLOSE_GOING_AWAY : Int = 1001; 35 | public inline static var CLOSE_PROTOCOL_ERROR : Int = 1002; 36 | public inline static var CLOSE_UNSUPPORTED : Int = 1003; 37 | public inline static var CLOSE_NO_STATUS : Int = 1005; 38 | public inline static var CLOSE_ABNORMAL : Int = 1006; 39 | public inline static var CLOSE_TOO_LARGE : Int = 1009; 40 | 41 | public var code(default, null) : Int; 42 | public var reason(default, null) : String; 43 | public var wasClean(default, null) : Bool; 44 | 45 | public function new(code : Int, reason : String, wasClean : Bool) 46 | { 47 | super(Event.CLOSE, false, false); 48 | 49 | this.code = code; 50 | this.reason = reason; 51 | this.wasClean = wasClean; 52 | } 53 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/utils/BaseCode64.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.utils; 24 | 25 | 26 | #if (haxe_211 || haxe3) 27 | import haxe.crypto.BaseCode; 28 | #else 29 | import haxe.BaseCode; 30 | #end 31 | 32 | 33 | import haxe.io.Bytes; 34 | #if openfl 35 | import flash.utils.ByteArray; 36 | #else 37 | import nme.utils.ByteArray; 38 | #end 39 | 40 | 41 | // Thanks to Richard Janicek (http://haxe.org/forum/thread/3395#nabble-td6608415) 42 | class BaseCode64 { 43 | 44 | private static inline var BASE_64_ENCODINGS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 45 | private static inline var BASE_64_PADDING = "="; 46 | 47 | public static function encodeByteArray( byteArray : ByteArray ) : String { 48 | #if flash 49 | var bytes = Bytes.ofData(byteArray); 50 | #else 51 | var bytes = byteArray; 52 | #end 53 | var encodings = Bytes.ofString(BASE_64_ENCODINGS); 54 | var base64 = new BaseCode(encodings).encodeBytes(bytes).toString(); 55 | 56 | var remainder = base64.length % 4; 57 | 58 | if (remainder > 1) { 59 | base64 += BASE_64_PADDING; 60 | } 61 | 62 | if (remainder == 2) { 63 | base64 += BASE_64_PADDING; 64 | } 65 | 66 | return base64; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Extension/com/gemioli/io/transports/WebSocketTransport.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.transports; 24 | 25 | import com.gemioli.io.net.WebSocket; 26 | import com.gemioli.io.Transport; 27 | import com.gemioli.io.events.TransportEvent; 28 | 29 | /** 30 | * ... 31 | * @author dimanux 32 | */ 33 | 34 | class WebSocketTransport extends Transport 35 | { 36 | public function new(host : String, port : String, secure : Bool, sessionId : String, query : String) 37 | { 38 | super(host, port, secure, sessionId); 39 | name = "websocket"; 40 | var queryPart = if (query != null && query.length > 0) '&'+query else ''; 41 | _url = (_secure ? "wss://" : "ws://") + _host + (_port == "" ? (_secure ? "443" : ":80") : (":" + _port)) + "/socket.io/1/websocket/" + _sessionId + "/?t=" + Transport.counter + queryPart; 42 | } 43 | 44 | override public function send(message : String) : Void 45 | { 46 | if (_socket != null) 47 | _socket.send(message); 48 | } 49 | 50 | override public function open() : Void 51 | { 52 | if (_socket != null) 53 | return; 54 | _socket = new WebSocket(_url); 55 | _socket.onopen = onOpen; 56 | _socket.onmessage = onMessage; 57 | _socket.onclose = onClose; 58 | _socket.onerror = onError; 59 | #if !js 60 | _socket.connect(); 61 | #end 62 | } 63 | 64 | override public function close() : Void 65 | { 66 | if (_socket == null) 67 | return; 68 | _socket.close(); 69 | _socket = null; 70 | } 71 | 72 | private function onMessage(event : Dynamic) : Void 73 | { 74 | if (_socket != null) 75 | decode(event.data); 76 | } 77 | 78 | private function onOpen(event : Dynamic) : Void 79 | { 80 | if (_socket != null) 81 | dispatchEvent(new TransportEvent(TransportEvent.OPENED)); 82 | } 83 | 84 | private function onClose(event : Dynamic) : Void 85 | { 86 | if (_socket != null) 87 | { 88 | trace("WebSocket transport closed: [" + event.code + "] " + event.reason); 89 | dispatchEvent(new TransportEvent(TransportEvent.CLOSED)); 90 | } 91 | } 92 | 93 | private function onError(event : Dynamic) : Void 94 | { 95 | if (_socket != null) 96 | trace("WebSocket transport error: " + event.message); 97 | } 98 | 99 | private var _url : String; 100 | private var _socket : WebSocket; 101 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/utils/URLParser.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.utils; 24 | 25 | import haxe.Http; 26 | 27 | /** 28 | * It's an URLParser by Mike Cann (http://haxe.org/doc/snip/uri_parser) with security part 29 | */ 30 | class URLParser 31 | { 32 | // Publics 33 | public var url : String; 34 | public var source : String; 35 | public var protocol : String; 36 | public var authority : String; 37 | public var userInfo : String; 38 | public var user : String; 39 | public var password : String; 40 | public var host : String; 41 | public var port : String; 42 | public var relative : String; 43 | public var path : String; 44 | public var directory : String; 45 | public var file : String; 46 | public var query : String; 47 | public var anchor : String; 48 | public var secure : Bool; 49 | 50 | // Privates 51 | inline static private function _parts() : Array { 52 | return ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"]; 53 | } 54 | 55 | public function new(url:String) 56 | { 57 | // Save for 'ron 58 | this.url = url; 59 | 60 | // The almighty regexp (courtesy of http://blog.stevenlevithan.com/archives/parseuri) 61 | var r : EReg = ~/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; 62 | 63 | // Match the regexp to the url 64 | r.match(url); 65 | 66 | // Use reflection to set each part 67 | for (i in 0..._parts().length) 68 | { 69 | Reflect.setField(this, _parts()[i], r.matched(i)); 70 | } 71 | 72 | if (protocol == "https" || protocol == "wss") 73 | secure = true; 74 | else 75 | secure = false; 76 | 77 | if (host == null) 78 | host = "localhost"; 79 | if (port == null || Std.parseInt(port) == null) 80 | port = ""; 81 | } 82 | 83 | public function toString() : String 84 | { 85 | var s : String = "For Url -> " + url + "\n"; 86 | for (i in 0..._parts().length) 87 | { 88 | s += _parts()[i] + ": " + Reflect.field(this, _parts()[i]) + (i==_parts().length-1?"":"\n"); 89 | } 90 | s += "\nsecure: " + secure; 91 | return s; 92 | } 93 | 94 | public static function parse(url:String) : URLParser 95 | { 96 | return new URLParser(url); 97 | } 98 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/Transport.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io; 24 | 25 | import com.gemioli.io.events.TransportEvent; 26 | import haxe.Utf8; 27 | import com.gemioli.io.utils.Utils; 28 | 29 | #if openfl 30 | import flash.events.EventDispatcher; 31 | #else 32 | import nme.events.EventDispatcher; 33 | #end 34 | 35 | 36 | class Transport extends EventDispatcher 37 | { 38 | public static var counter(get_counter, null) : Int = 0; 39 | public var name(default, null) : String; 40 | 41 | public function new(host : String, port : String, secure : Bool, sessionId : String) 42 | { 43 | super(); 44 | _host = host; 45 | _port = port; 46 | _secure = secure; 47 | _sessionId = sessionId; 48 | _data = ""; 49 | _dataLength = -1; 50 | var encodeTerminator = new Utf8(); 51 | encodeTerminator.addChar(0xfffd); 52 | _encodeTerminator = encodeTerminator.toString(); 53 | } 54 | 55 | public function send(message : String) : Void 56 | { 57 | } 58 | 59 | public function open() : Void 60 | { 61 | } 62 | 63 | public function close() : Void 64 | { 65 | } 66 | 67 | private function decode(?data : String) : Void 68 | { 69 | if (data != null) 70 | _data += data; 71 | while (_data.length > 0) 72 | { 73 | if (_dataLength == -1) 74 | { 75 | var dataLengthString : String = ""; 76 | for (i in 0...Utf8.length(_data)) 77 | { 78 | var ch = Utf8.charCodeAt(_data, i); 79 | if (i == 0) 80 | { 81 | if (ch != 0xfffd) 82 | { 83 | var message = _data; 84 | _data = ""; 85 | dispatchEvent(new TransportEvent(TransportEvent.MESSAGE, false, false, message)); 86 | break; 87 | } 88 | } 89 | else if (i > 0) 90 | { 91 | if (ch == 0xfffd) 92 | { 93 | _data = Utils.Utf8Substr(_data, i + 1, Utf8.length(_data) - i - 1); 94 | _dataLength = Std.parseInt(dataLengthString); 95 | break; 96 | } 97 | else 98 | dataLengthString += String.fromCharCode(ch); 99 | } 100 | } 101 | } 102 | else 103 | { 104 | if (_dataLength <= Utf8.length(_data)) 105 | { 106 | var message = Utils.Utf8Substr(_data, 0, _dataLength); 107 | _data = Utils.Utf8Substr(_data, _dataLength, Utf8.length(_data) - _dataLength); 108 | _dataLength = -1; 109 | dispatchEvent(new TransportEvent(TransportEvent.MESSAGE, false, false, message)); 110 | } 111 | } 112 | } 113 | } 114 | 115 | private function encode(messages : Array) : String 116 | { 117 | if (messages.length == 1) 118 | return messages[0]; 119 | var encodedString = ""; 120 | for (message in messages) 121 | encodedString += (_encodeTerminator + Std.string(message.length) + _encodeTerminator + message); 122 | return encodedString; 123 | } 124 | 125 | private static function get_counter() : Int 126 | { 127 | return counter++; 128 | } 129 | 130 | private var _host : String; 131 | private var _port : String; 132 | private var _secure : Bool; 133 | private var _sessionId : String; 134 | private var _data : String; 135 | private var _dataLength : Int; 136 | private var _encodeTerminator : String; 137 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/transports/XHRPollingTransport.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.transports; 24 | 25 | import com.gemioli.io.events.TransportEvent; 26 | import com.gemioli.io.Transport; 27 | 28 | #if openfl 29 | import flash.errors.SecurityError; 30 | import flash.events.Event; 31 | import flash.events.HTTPStatusEvent; 32 | import flash.events.IOErrorEvent; 33 | import flash.events.SecurityErrorEvent; 34 | import flash.net.URLLoader; 35 | import flash.net.URLRequest; 36 | import flash.net.URLRequestMethod; 37 | #else 38 | import nme.errors.SecurityError; 39 | import nme.events.Event; 40 | import nme.events.HTTPStatusEvent; 41 | import nme.events.IOErrorEvent; 42 | import nme.events.SecurityErrorEvent; 43 | import nme.net.URLLoader; 44 | import nme.net.URLRequest; 45 | import nme.net.URLRequestMethod; 46 | #end 47 | 48 | 49 | 50 | class XHRPollingTransport extends Transport 51 | { 52 | public function new(host : String, port : String, secure : Bool, sessionId : String, query : String) 53 | { 54 | super(host, port, secure, sessionId); 55 | name = "xhr-polling"; 56 | var queryPart = if (query != null && query.length > 0) '&'+query else ''; 57 | _url = (_secure ? "https://" : "http://") + _host + (_port == "" ? "" : (":" + _port)) + "/socket.io/1/xhr-polling/" + _sessionId + "/?t=" + queryPart; 58 | _messagesBuffer = new Array(); 59 | } 60 | 61 | override public function send(message : String) : Void 62 | { 63 | _messagesBuffer.push(message); 64 | nextSend(); 65 | } 66 | 67 | override public function open() : Void 68 | { 69 | if (_recvLoader != null) 70 | return; 71 | 72 | _recvRequest = new URLRequest(); 73 | _recvRequest.method = URLRequestMethod.GET; 74 | _recvLoader = new URLLoader(); 75 | _recvLoader.addEventListener(Event.COMPLETE, onRecvComplete); 76 | _recvLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onRecvStatus); 77 | _recvLoader.addEventListener(IOErrorEvent.IO_ERROR, onRecvError); 78 | _recvLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onRecvSecurityError); 79 | 80 | _sendRequest = new URLRequest(); 81 | _sendRequest.method = URLRequestMethod.POST; 82 | _sendLoader = new URLLoader(); 83 | _sendLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onSendStatus); 84 | _sendLoader.addEventListener(IOErrorEvent.IO_ERROR, onSendError); 85 | _sendLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSendSecurityError); 86 | 87 | nextRecv(); 88 | 89 | if (_recvLoader != null) 90 | dispatchEvent(new TransportEvent(TransportEvent.OPENED)); 91 | } 92 | 93 | override public function close() : Void 94 | { 95 | if (_recvLoader != null) 96 | { 97 | _recvLoader = null; 98 | } 99 | if (_sendLoader != null) 100 | { 101 | _sendLoader = null; 102 | } 103 | dispatchEvent(new TransportEvent(TransportEvent.CLOSED)); 104 | } 105 | 106 | private function nextRecv() : Void 107 | { 108 | if (_recvLoader != null) 109 | { 110 | _recvRequest.url = _url + Transport.counter; 111 | try 112 | { 113 | _recvLoader.load(_recvRequest); 114 | } 115 | catch (error : SecurityError) 116 | { 117 | close(); 118 | } 119 | } 120 | } 121 | 122 | private function onRecvComplete(event : Event) : Void 123 | { 124 | if (_recvLoader != null) 125 | { 126 | decode(event.target.data); 127 | nextRecv(); 128 | } 129 | } 130 | 131 | private function onRecvStatus(event : HTTPStatusEvent) : Void 132 | { 133 | if (event.status != 200 && _recvLoader != null) 134 | { 135 | traceError("Recv stream status error[" + event.status + "]."); 136 | close(); 137 | } 138 | } 139 | 140 | private function onRecvError(event : IOErrorEvent) : Void 141 | { 142 | if (_recvLoader != null) 143 | { 144 | traceError("Recv stream IO error."); 145 | close(); 146 | } 147 | } 148 | 149 | private function onRecvSecurityError(event : SecurityErrorEvent) : Void 150 | { 151 | if (_recvLoader != null) 152 | { 153 | traceError("Recv stream security error."); 154 | close(); 155 | } 156 | } 157 | 158 | private function nextSend() : Void 159 | { 160 | if (_sendLoader == null) 161 | // Closed 162 | return; 163 | if (_sendLoader.hasEventListener(Event.COMPLETE)) 164 | // Sending in progress 165 | return; 166 | if (_messagesBuffer.length == 0) 167 | // Nothing to send 168 | return; 169 | _sendRequest.url = _url + Transport.counter; 170 | _sendRequest.data = encode(_messagesBuffer); 171 | _messagesBuffer.splice(0, _messagesBuffer.length); 172 | _sendLoader.addEventListener(Event.COMPLETE, onSendComplete); 173 | try 174 | { 175 | _sendLoader.load(_sendRequest); 176 | } 177 | catch (error : SecurityError) 178 | { 179 | close(); 180 | } 181 | } 182 | private function onSendComplete(event : Event) : Void 183 | { 184 | if (_sendLoader != null) 185 | { 186 | _sendLoader.removeEventListener(Event.COMPLETE, onSendComplete); 187 | nextSend(); 188 | } 189 | } 190 | 191 | private function onSendStatus(event : HTTPStatusEvent) : Void 192 | { 193 | if (event.status != 200 && _sendLoader != null) 194 | { 195 | traceError("Send stream status error[" + event.status + "]."); 196 | close(); 197 | } 198 | } 199 | 200 | private function onSendError(event : IOErrorEvent) : Void 201 | { 202 | if (_sendLoader != null) 203 | { 204 | traceError("Send stream IO error."); 205 | close(); 206 | } 207 | } 208 | 209 | private function onSendSecurityError(event : SecurityErrorEvent) : Void 210 | { 211 | if (_sendLoader != null) 212 | { 213 | traceError("Send stream security error."); 214 | close(); 215 | } 216 | } 217 | 218 | private function traceError(message : String) : Void 219 | { 220 | trace("xhr-polling transport error: " + message); 221 | } 222 | 223 | private var _url : String; 224 | private var _recvRequest : URLRequest; 225 | private var _recvLoader : URLLoader; 226 | private var _sendRequest : URLRequest; 227 | private var _sendLoader : URLLoader; 228 | private var _messagesBuffer : Array; 229 | } -------------------------------------------------------------------------------- /Project/Source/com/gemioli/ExtensionTest.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli; 24 | 25 | #if openfl 26 | import flash.display.Sprite; 27 | import flash.display.StageAlign; 28 | import flash.display.StageScaleMode; 29 | import flash.events.Event; 30 | import flash.Lib; 31 | #else 32 | import nme.display.Sprite; 33 | import nme.display.StageAlign; 34 | import nme.display.StageScaleMode; 35 | import nme.events.Event; 36 | import nme.Lib; 37 | #end 38 | 39 | import com.gemioli.io.Socket; 40 | import com.gemioli.io.events.SocketEvent; 41 | 42 | class ExtensionTest extends Sprite 43 | { 44 | public function new() 45 | { 46 | super(); 47 | addEventListener(Event.ADDED_TO_STAGE, init); 48 | } 49 | 50 | private function init(event : Event) : Void 51 | { 52 | removeEventListener(Event.ADDED_TO_STAGE, init); 53 | addEventListener(Event.ENTER_FRAME, onUpdate); 54 | Lib.current.graphics.beginFill(0x000000); 55 | Lib.current.graphics.drawRect(0, 0, Lib.current.stage.stageWidth, Lib.current.stage.stageHeight); 56 | Lib.current.graphics.endFill(); 57 | graphics.beginFill(0xffffff); 58 | graphics.drawRect( -20, -20, 40, 40); 59 | graphics.endFill(); 60 | 61 | _socket = new Socket("http://localhost:8080"); 62 | //_socket = new Socket("http://socketioserver-dimanux.dotcloud.com"); 63 | _socket.addEventListener(SocketEvent.CONNECTING, function(event : SocketEvent) : Void { 64 | trace("Connecting..."); 65 | }); 66 | _socket.addEventListener(SocketEvent.CONNECT, function(event : SocketEvent) : Void { 67 | trace("Connected"); 68 | }); 69 | _socket.addEventListener(SocketEvent.CONNECT_FAILED, function(event : SocketEvent) : Void { 70 | trace("Connect failed"); 71 | }); 72 | _socket.addEventListener(SocketEvent.DISCONNECTING, function(event : SocketEvent) : Void { 73 | trace("Disconnecting..."); 74 | }); 75 | _socket.addEventListener(SocketEvent.DISCONNECT, function(event : SocketEvent) : Void { 76 | trace("Disconnected"); 77 | }); 78 | _socket.addEventListener(SocketEvent.ERROR, function(event : SocketEvent) : Void { 79 | trace("Error: " + event.args.reason + " " + event.args.advice); 80 | }); 81 | _socket.addEventListener(SocketEvent.MESSAGE, function(event : SocketEvent) : Void { 82 | if (event.args == "Hello") 83 | { 84 | trace("Hello from server!"); 85 | _socket.send("Hi"); 86 | } 87 | }); 88 | _socket.addEventListener(SocketEvent.RECONNECTING, function(event : SocketEvent) : Void { 89 | trace("Reconnecting..."); 90 | }); 91 | _socket.addEventListener(SocketEvent.RECONNECT, function(event : SocketEvent) : Void { 92 | trace("Reconnected"); 93 | }); 94 | _socket.addEventListener(SocketEvent.RECONNECT_FAILED, function(event : SocketEvent) : Void { 95 | trace("Reconnect failed"); 96 | }); 97 | _socket.addEventListener("ServerEvent", function(event : SocketEvent) : Void { 98 | trace("Event [ServerEvent] Data [" + event.args[0].name + "]"); 99 | _socket.emit("ClientEventEmpty"); 100 | _socket.emit("ClientEventData", { myData : "Data" } ); 101 | _socket.emit("ClientEventCallback", null, function(data : Dynamic) : Void { 102 | trace("Callback data[" + data[0] + "]"); 103 | trace("Starting ping-pong..."); 104 | _pingId = 0; 105 | _socket.emit("Ping", _pingId); 106 | }); 107 | }); 108 | _socket.addEventListener("Pong", function (event : SocketEvent) : Void { 109 | trace("Received pong " + event.args[0]); 110 | trace("Sending ping " + (++_pingId)); 111 | _socket.emit("Ping", _pingId); 112 | }); 113 | _socket.connect(); 114 | 115 | _socketChat = new Socket("http://localhost:8080/chat", {transports : ["xhr-polling"]}); 116 | //_socketChat = new Socket("http://socketioserver-dimanux.dotcloud.com/chat", {transports : ["xhr-polling"]} ); 117 | _socketChat.addEventListener(SocketEvent.CONNECTING, function(event : SocketEvent) : Void { 118 | trace("Chat Connecting..."); 119 | }); 120 | _socketChat.addEventListener(SocketEvent.CONNECT, function(event : SocketEvent) : Void { 121 | trace("Chat Connected"); 122 | _socketChat.send("Hi chat!"); 123 | }); 124 | _socketChat.addEventListener(SocketEvent.CONNECT_FAILED, function(event : SocketEvent) : Void { 125 | trace("Chat Connect failed"); 126 | }); 127 | _socketChat.addEventListener(SocketEvent.DISCONNECTING, function(event : SocketEvent) : Void { 128 | trace("Chat Disconnecting..."); 129 | }); 130 | _socketChat.addEventListener(SocketEvent.DISCONNECT, function(event : SocketEvent) : Void { 131 | trace("Chat Disconnected"); 132 | }); 133 | _socketChat.addEventListener(SocketEvent.ERROR, function(event : SocketEvent) : Void { 134 | trace("Chat Error: " + event.args.reason + " " + event.args.advice); 135 | }); 136 | _socketChat.addEventListener(SocketEvent.MESSAGE, function(event : SocketEvent) : Void { 137 | trace("Message from chat: [" + event.args + "]"); 138 | }); 139 | _socketChat.addEventListener(SocketEvent.RECONNECTING, function(event : SocketEvent) : Void { 140 | trace("Chat Reconnecting..."); 141 | }); 142 | _socketChat.addEventListener(SocketEvent.RECONNECT, function(event : SocketEvent) : Void { 143 | trace("Chat Reconnected"); 144 | }); 145 | _socketChat.addEventListener(SocketEvent.RECONNECT_FAILED, function(event : SocketEvent) : Void { 146 | trace("Chat Reconnect failed"); 147 | }); 148 | _socketChat.connect(); 149 | } 150 | 151 | private function onUpdate(e:Event) : Void 152 | { 153 | x = Lib.current.stage.stageWidth / 2; 154 | y = Lib.current.stage.stageHeight / 2; 155 | rotation += 1; 156 | } 157 | 158 | private var _socket : Socket; 159 | private var _socketChat : Socket; 160 | private var _pingId : Int; 161 | 162 | public static function main() 163 | { 164 | var stage = Lib.current.stage; 165 | #if openfl 166 | stage.scaleMode = flash.display.StageScaleMode.NO_SCALE; 167 | stage.align = flash.display.StageAlign.TOP_LEFT; 168 | #else 169 | stage.scaleMode = nme.display.StageScaleMode.NO_SCALE; 170 | stage.align = nme.display.StageAlign.TOP_LEFT; 171 | #end 172 | 173 | Lib.current.addChild(new ExtensionTest ()); 174 | } 175 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/SocketProxy.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io; 24 | 25 | import com.gemioli.io.events.SocketEvent; 26 | import com.gemioli.io.events.SocketProxyEvent; 27 | import com.gemioli.io.events.TransportEvent; 28 | import com.gemioli.io.Transport; 29 | import com.gemioli.io.transports.WebSocketTransport; 30 | import com.gemioli.io.transports.XHRPollingTransport; 31 | import com.gemioli.io.utils.Utils; 32 | import haxe.Utf8; 33 | 34 | #if openfl 35 | import flash.events.EventDispatcher; 36 | import flash.events.SecurityErrorEvent; 37 | import flash.net.URLLoader; 38 | import flash.net.URLRequest; 39 | import flash.net.URLRequestMethod; 40 | import flash.events.Event; 41 | import flash.events.IOErrorEvent; 42 | import flash.events.HTTPStatusEvent; 43 | #else 44 | import nme.events.EventDispatcher; 45 | import nme.events.SecurityErrorEvent; 46 | import nme.net.URLLoader; 47 | import nme.net.URLRequest; 48 | import nme.net.URLRequestMethod; 49 | import nme.events.Event; 50 | import nme.events.IOErrorEvent; 51 | import nme.events.HTTPStatusEvent; 52 | #end 53 | 54 | 55 | 56 | 57 | class SocketProxy extends EventDispatcher 58 | { 59 | public var connectionStatus(default, null) : SocketConnectionStatus; 60 | 61 | public static function connectSocket(socket : Socket) : SocketProxy 62 | { 63 | var proxy = getProxy(socket.host, socket.port, socket.secure, socket.transport, socket.query); 64 | for (endpoint in proxy._endpoints) 65 | if (endpoint == socket.endpoint) 66 | return null; 67 | proxy._endpoints.push(socket.endpoint); 68 | proxy.connect(); 69 | return proxy; 70 | } 71 | 72 | public static function disconnectSocket(socket : Socket) : Void 73 | { 74 | var proxy = getProxy(socket.host, socket.port, socket.secure, socket.transport, socket.query); 75 | for (endpoint in proxy._endpoints) 76 | if (endpoint == socket.endpoint) 77 | { 78 | proxy._endpoints.remove(socket.endpoint); 79 | break; 80 | } 81 | if (proxy._endpoints.length == 0) 82 | { 83 | _proxies.remove(proxy._name); 84 | proxy.disconnect(); 85 | } 86 | } 87 | 88 | public function sendMessage(message : String) : Void 89 | { 90 | if (connectionStatus == SocketConnectionStatus.CONNECTED && _transport != null) 91 | { 92 | _transport.send(message); 93 | } 94 | } 95 | 96 | private static function getProxy(host : String, port : String, secure : Bool, transport : String, query: String) : SocketProxy 97 | { 98 | var name = (secure ? "https" : "http") + "://" + host + (port == "" ? "" : (":" + port)) + "/" + transport; 99 | if (!_proxies.exists(name)) 100 | _proxies.set(name, new SocketProxy(name, host, port, secure, transport, query)); 101 | return _proxies.get(name); 102 | } 103 | 104 | private function new(name : String, host : String, port : String, secure : Bool, transport : String, query: String) 105 | { 106 | super(); 107 | 108 | _name = name; 109 | _host = host; 110 | _port = port; 111 | _secure = secure; 112 | _query = query; 113 | _transportName = transport; 114 | _endpoints = new Array(); 115 | connectionStatus = SocketConnectionStatus.DISCONNECTED; 116 | _transport = null; 117 | } 118 | 119 | private function connect() : Void 120 | { 121 | if (connectionStatus != SocketConnectionStatus.DISCONNECTED) 122 | return; 123 | connectionStatus = SocketConnectionStatus.CONNECTING; 124 | 125 | var handshakeRequest = new URLRequest(); 126 | handshakeRequest.url = (_secure ? "https://" : "http://") + _host + (_port == "" ? "" : (":" + _port)) + "/socket.io/1/?t=" + Transport.counter; 127 | handshakeRequest.method = URLRequestMethod.GET; 128 | _handshakeLoader = new URLLoader(); 129 | _handshakeLoader.addEventListener(Event.COMPLETE, onHandshake); 130 | _handshakeLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHandshakeStatus); 131 | _handshakeLoader.addEventListener(IOErrorEvent.IO_ERROR, onHandshakeError); 132 | _handshakeLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onHandshakeSecurityError); 133 | _handshakeLoader.load(handshakeRequest); 134 | } 135 | 136 | private function onHandshake(event : Event) : Void 137 | { 138 | var responseData : String = event.target.data; 139 | var responseArray = responseData.split(":"); 140 | if (responseArray.length != 4) 141 | { 142 | disconnectEndpoints(); 143 | return; 144 | } 145 | _sessionId = responseArray[0]; 146 | _heartbeatTimeout = Std.parseInt(responseArray[1]); 147 | _closeTimeout = Std.parseInt(responseArray[2]); 148 | var transports = responseArray[3].split(","); 149 | for (transport in transports) 150 | if (transport == _transportName) 151 | { 152 | tryTransport(); 153 | return; 154 | } 155 | disconnectEndpoints(); 156 | } 157 | 158 | private function onHandshakeStatus(event : HTTPStatusEvent) : Void 159 | { 160 | if (event.status != 200 && connectionStatus == SocketConnectionStatus.CONNECTING) 161 | { 162 | dispatchErrorEvent("Handshake status error [" + event.status + "]", "Check server status."); 163 | disconnectEndpoints(); 164 | } 165 | } 166 | 167 | private function onHandshakeError(event : IOErrorEvent) : Void 168 | { 169 | if (connectionStatus == SocketConnectionStatus.CONNECTING) 170 | { 171 | dispatchErrorEvent("Handshake IO error.", "Check connection."); 172 | disconnectEndpoints(); 173 | } 174 | } 175 | 176 | private function onHandshakeSecurityError(event : SecurityErrorEvent) : Void 177 | { 178 | if (connectionStatus == SocketConnectionStatus.CONNECTING) 179 | { 180 | dispatchErrorEvent("Handshake security error [" + event.text + "].", "Check flash policy server."); 181 | disconnectEndpoints(); 182 | } 183 | } 184 | 185 | private function dispatchErrorEvent(reason : String, advice : String) : Void 186 | { 187 | var message = "[" + _transportName + "] " + reason + "+" + advice; 188 | var endpoints = _endpoints.copy(); 189 | for (endpoint in endpoints) 190 | dispatchEvent(new SocketProxyEvent(endpoint, 7, "", message)); 191 | } 192 | 193 | private function disconnectEndpoints() : Void 194 | { 195 | connectionStatus = SocketConnectionStatus.DISCONNECTED; 196 | var endpoints = _endpoints.copy(); 197 | for (endpoint in endpoints) 198 | dispatchEvent(new SocketProxyEvent(endpoint, 0, "", "")); 199 | } 200 | 201 | private function disconnect() : Void 202 | { 203 | if (connectionStatus == SocketConnectionStatus.DISCONNECTED) 204 | return; 205 | connectionStatus = SocketConnectionStatus.DISCONNECTING; 206 | if (_transport != null) 207 | { 208 | _transport.close(); 209 | } 210 | } 211 | 212 | private function tryTransport() : Void 213 | { 214 | switch (_transportName) 215 | { 216 | case "websocket": 217 | _transport = new WebSocketTransport(_host, _port, _secure, _sessionId, _query); 218 | case "xhr-polling": 219 | _transport = new XHRPollingTransport(_host, _port, _secure, _sessionId, _query); 220 | default: 221 | { 222 | disconnectEndpoints(); 223 | return; 224 | } 225 | } 226 | 227 | _transport.addEventListener(TransportEvent.OPENED, onTransportOpened); 228 | _transport.addEventListener(TransportEvent.CLOSED, onTransportClosed); 229 | _transport.addEventListener(TransportEvent.MESSAGE, onTransportMessage); 230 | _transport.open(); 231 | } 232 | 233 | private function onTransportOpened(event : TransportEvent) : Void 234 | { 235 | connectionStatus = SocketConnectionStatus.CONNECTED; 236 | } 237 | 238 | private function onTransportClosed(event : TransportEvent) : Void 239 | { 240 | _transport.removeEventListener(TransportEvent.OPENED, onTransportOpened); 241 | _transport.removeEventListener(TransportEvent.CLOSED, onTransportClosed); 242 | _transport.removeEventListener(TransportEvent.MESSAGE, onTransportMessage); 243 | _transport = null; 244 | disconnectEndpoints(); 245 | } 246 | 247 | private function onTransportMessage(event : TransportEvent) : Void 248 | { 249 | var messageParts = new Array(); 250 | var dotsPosition = -1; 251 | var lastPosition = 0; 252 | for (i in 0...3) 253 | { 254 | dotsPosition = event.message.indexOf(":", lastPosition); 255 | if (dotsPosition != -1) 256 | { 257 | messageParts.push(Utils.Utf8Substr(event.message, lastPosition, dotsPosition - lastPosition)); 258 | lastPosition = dotsPosition + 1; 259 | } 260 | } 261 | messageParts.push(Utils.Utf8Substr(event.message, lastPosition, Utf8.length(event.message) - lastPosition)); 262 | 263 | var messageId = Std.parseInt(messageParts[0]); 264 | if (messageParts.length < 3 || messageId == null) 265 | // Unknown message 266 | return; 267 | if (messageId == 1 && messageParts[2] == "") 268 | { 269 | for (endpoint in _endpoints) 270 | if (endpoint != "") 271 | sendMessage("1::" + endpoint); 272 | } 273 | else if (messageId == 0 && messageParts[2] == "") 274 | { 275 | var endpoints = _endpoints.copy(); 276 | for (endpoint in endpoints) 277 | dispatchEvent(new SocketProxyEvent(endpoint, 0, "", "")); 278 | } 279 | else if (messageId == 2 && messageParts[2] == "") //heartbeat without endpoint 280 | { 281 | var endpoints = _endpoints.copy(); 282 | for (endpoint in endpoints) 283 | dispatchEvent(new SocketProxyEvent(endpoint, Std.parseInt(messageParts[0]), messageParts[1], messageParts.length > 3 ? messageParts[3] : "")); 284 | } 285 | dispatchEvent(new SocketProxyEvent(messageParts[2], Std.parseInt(messageParts[0]), messageParts[1], messageParts.length > 3 ? messageParts[3] : "")); 286 | } 287 | 288 | private var _name : String; 289 | private var _host : String; 290 | private var _port : String; 291 | private var _query : String; 292 | private var _secure : Bool; 293 | private var _endpoints : Array; 294 | private var _handshakeLoader : URLLoader; 295 | private var _transportName : String; 296 | private var _transport : Transport; 297 | 298 | private var _sessionId : String; 299 | private var _heartbeatTimeout : Int; 300 | private var _closeTimeout : Int; 301 | 302 | // Static proxies 303 | private static var _proxies : Map = new Map(); 304 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/Socket.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io; 24 | 25 | import com.gemioli.io.events.SocketProxyEvent; 26 | import com.gemioli.io.SocketProxy; 27 | import com.gemioli.io.events.SocketEvent; 28 | import com.gemioli.io.utils.URLParser; 29 | import com.gemioli.io.utils.Utils; 30 | 31 | #if openfl 32 | import flash.events.EventDispatcher; 33 | import flash.events.TimerEvent; 34 | import flash.utils.Timer; 35 | #else 36 | import nme.events.EventDispatcher; 37 | import nme.events.TimerEvent; 38 | import nme.utils.Timer; 39 | #end 40 | 41 | 42 | import haxe.Json; 43 | import haxe.Utf8; 44 | 45 | 46 | #if !haxe3 47 | typedef Hash = Map; 48 | #end 49 | 50 | 51 | class Socket extends EventDispatcher 52 | { 53 | public var connectionStatus(default, null) : SocketConnectionStatus; 54 | public var host(default, null) : String; 55 | public var port(default, null) : String; 56 | public var transport(get_transport, null) : String; 57 | public var secure(default, null) : Bool; 58 | public var endpoint(default, null) : String; 59 | public var query(default, null) : String; 60 | 61 | private var onceMap:MapVoid> = new Map(); 62 | 63 | public function new(uri : String, options : Dynamic = null) 64 | { 65 | super(); 66 | 67 | connectionStatus = SocketConnectionStatus.DISCONNECTED; 68 | var uriParsed = new URLParser(uri); 69 | secure = uriParsed.secure; 70 | host = uriParsed.host; 71 | port = uriParsed.port; 72 | endpoint = uriParsed.path; 73 | query = uriParsed.query; 74 | _uri = uri; 75 | _buffer = new Array(); 76 | _ack = 0; 77 | _callbacks = new Map Void > (); 78 | 79 | if (options != null) 80 | { 81 | if (Std.is(options.reconnect, Bool)) 82 | _reconnect = options.reconnect; 83 | if (Std.is(options.reconnectionAttempts, Int)) 84 | _maxReconnectionAttempts = options.reconnectionAttempts; 85 | if (Std.is(options.reconnectionDelay, Int)) 86 | _reconnectionDelay = options.reconnectionDelay; 87 | if (Std.is(options.connectTimeout, Int)) 88 | _connectTimeout = options.connectTimeout; 89 | if (Std.is(options.transports, Array)) 90 | _transports = options.transports; 91 | if (Std.is(options.flashPolicyPort, Int)) 92 | _flashPolicyPort = options.flashPolicyPort; 93 | if (Std.is(options.flashPolicyUrl, String)) 94 | _flashPolicyUrl = options.flashPolicyUrl; 95 | } 96 | 97 | #if flash 98 | flash.system.Security.allowDomain("*"); 99 | if (_flashPolicyUrl == null) 100 | _flashPolicyUrl = "xmlsocket://" + uriParsed.host + ":" + _flashPolicyPort; 101 | flash.system.Security.loadPolicyFile(_flashPolicyUrl); 102 | #end 103 | 104 | if (_transports == null || _transports.length == 0) 105 | { 106 | _transports = new Array(); 107 | _transports.push("websocket"); 108 | _transports.push("xhr-polling"); 109 | } 110 | 111 | _connectTimer = new Timer(_connectTimeout); 112 | _connectTimer.addEventListener(TimerEvent.TIMER, onConnectTimeout); 113 | _reconnectionAttemptsLeft = _maxReconnectionAttempts; 114 | _reconnectTimer = new Timer(_reconnectionDelay); 115 | _reconnectTimer.addEventListener(TimerEvent.TIMER, onReconnect); 116 | } 117 | 118 | private function isReconnecting() : Bool 119 | { 120 | return _reconnectionAttemptsLeft != _maxReconnectionAttempts; 121 | } 122 | 123 | public function connect() : Void 124 | { 125 | if (connectionStatus != SocketConnectionStatus.DISCONNECTED) 126 | return; 127 | if (_reconnectTimer.running) 128 | { 129 | _reconnectTimer.reset(); 130 | _reconnectionAttemptsLeft = _maxReconnectionAttempts; 131 | } 132 | connectionStatus = SocketConnectionStatus.CONNECTING; 133 | if (isReconnecting()) 134 | dispatchEvent(new SocketEvent(SocketEvent.RECONNECTING)); 135 | else 136 | dispatchEvent(new SocketEvent(SocketEvent.CONNECTING)); 137 | _currentTransports = _transports.copy(); 138 | connectAttempt(); 139 | } 140 | 141 | private function connectAttempt() : Void 142 | { 143 | _proxy = SocketProxy.connectSocket(this); 144 | if (_proxy == null) 145 | tryNextTransport(); 146 | else 147 | { 148 | _connectTimer.reset(); 149 | _connectTimer.start(); 150 | _proxy.addEventListener(endpoint, onMessage); 151 | if (_proxy.connectionStatus == SocketConnectionStatus.CONNECTED) 152 | _proxy.sendMessage("1::" + endpoint); 153 | } 154 | } 155 | 156 | public function disconnect() : Void 157 | { 158 | if (_reconnectTimer.running) 159 | { 160 | _reconnectTimer.reset(); 161 | _reconnectionAttemptsLeft = _maxReconnectionAttempts; 162 | } 163 | if (_connectTimer.running) 164 | _connectTimer.stop(); 165 | if (connectionStatus == SocketConnectionStatus.DISCONNECTED || _proxy == null) 166 | return; 167 | connectionStatus = SocketConnectionStatus.DISCONNECTING; 168 | dispatchEvent(new SocketEvent(SocketEvent.DISCONNECTING)); 169 | if (endpoint != "") 170 | { 171 | _proxy.sendMessage("0::" + endpoint); 172 | } 173 | _proxy.removeEventListener(endpoint, onMessage); 174 | SocketProxy.disconnectSocket(this); 175 | _proxy = null; 176 | connectionStatus = SocketConnectionStatus.DISCONNECTED; 177 | dispatchEvent(new SocketEvent(SocketEvent.DISCONNECT)); 178 | } 179 | 180 | /** 181 | * Convenience method for addEventListener 182 | * @param event 183 | * @param callbackFunction 184 | */ 185 | public function on(event : String, callbackFunction : Dynamic->Void) : Void 186 | { 187 | addEventListener(event, function(e:SocketEvent) { 188 | socketCallback(e, callbackFunction); 189 | }); 190 | } 191 | 192 | /** 193 | * Convenience method that only listens once to the specified event, then removes itself. 194 | * WARNING: This method will override any previous uncalled listeners for the specified event name. 195 | * @param event 196 | * @param callbackFunction 197 | */ 198 | public function once(event : String, callbackFunction : Dynamic->Void) : Void 199 | { 200 | onceMap.set(event, callbackFunction); 201 | addEventListener(event, onceCallback); 202 | } 203 | function onceCallback(e : SocketEvent) : Void 204 | { 205 | var event = e.type; 206 | removeEventListener(event, onceCallback); 207 | var callbackFunction = onceMap.get(event); 208 | onceMap.remove(event); 209 | socketCallback(e, callbackFunction); 210 | } 211 | 212 | function socketCallback(e : SocketEvent, callbackFunction : Dynamic->Void) : Void 213 | { 214 | if (Std.is(e.args, Array)) { 215 | var a:Array = cast e.args; 216 | if (a.length == 1) { 217 | callbackFunction(a[0]); 218 | return; 219 | } 220 | } 221 | callbackFunction(e.args); 222 | } 223 | 224 | /** 225 | * WARNING: Only works for listeners added with once() for now. 226 | * @param event 227 | */ 228 | public function removeAllListeners( event : String) : Void { 229 | removeEventListener(event, onceCallback); 230 | onceMap.remove(event); 231 | } 232 | 233 | 234 | public function send(message : Dynamic, ?callbackFunction : Dynamic->Void = null) : Void 235 | { 236 | if (Std.is(message, String)) 237 | { 238 | if (callbackFunction != null) 239 | { 240 | var ack = Std.string(_ack++); 241 | _callbacks.set(ack, callbackFunction); 242 | _buffer.push("3:" + ack + "+:" + endpoint + ":" + Std.string(message)); 243 | } 244 | else 245 | _buffer.push("3::" + endpoint + ":" + Std.string(message)); 246 | } 247 | else 248 | { 249 | if (callbackFunction != null) 250 | { 251 | var ack = Std.string(_ack++); 252 | _callbacks.set(ack, callbackFunction); 253 | _buffer.push("4:" + ack + "+:" + endpoint + ":" + Json.stringify(message)); 254 | } 255 | else 256 | _buffer.push("4::" + endpoint + ":" + Json.stringify(message)); 257 | } 258 | sendMessages(); 259 | } 260 | 261 | public function emit(event : String, ?data : Dynamic = null, ?callbackFunction : Dynamic->Void = null) : Void 262 | { 263 | if (callbackFunction != null) 264 | { 265 | var ack = Std.string(_ack++); 266 | _callbacks.set(ack, callbackFunction); 267 | _buffer.push("5:" + ack + "+:" + endpoint + ":" + Json.stringify( { "name" : event, "args" : data } )); 268 | } 269 | else 270 | _buffer.push("5::" + endpoint + ":" + Json.stringify( { "name" : event, "args" : data } )); 271 | sendMessages(); 272 | } 273 | 274 | private function get_transport() : String 275 | { 276 | if (_currentTransports == null || _currentTransports.length == 0) 277 | return "unknown"; 278 | return _currentTransports[0]; 279 | } 280 | 281 | private function sendMessages() : Void 282 | { 283 | if (connectionStatus == SocketConnectionStatus.CONNECTED) 284 | { 285 | for (message in _buffer) 286 | _proxy.sendMessage(message); 287 | _buffer.splice(0, _buffer.length); 288 | } 289 | } 290 | 291 | private function onMessage(message : SocketProxyEvent) : Void 292 | { 293 | switch (message.id) 294 | { 295 | case 0: // disconnect 296 | { 297 | _proxy.removeEventListener(endpoint, onMessage); 298 | SocketProxy.disconnectSocket(this); 299 | _proxy = null; 300 | 301 | switch (connectionStatus) 302 | { 303 | case SocketConnectionStatus.CONNECTING: 304 | { 305 | _connectTimer.stop(); 306 | tryNextTransport(); 307 | } 308 | case SocketConnectionStatus.CONNECTED: 309 | { 310 | connectionStatus = SocketConnectionStatus.DISCONNECTED; 311 | dispatchEvent(new SocketEvent(SocketEvent.DISCONNECT)); 312 | 313 | if (_reconnect && _maxReconnectionAttempts > 0) 314 | { 315 | _reconnectionAttemptsLeft = _maxReconnectionAttempts; 316 | _reconnectTimer.start(); 317 | } 318 | } 319 | default: null; 320 | } 321 | } 322 | case 1: // connect 323 | { 324 | if (connectionStatus == SocketConnectionStatus.CONNECTING) 325 | { 326 | _connectTimer.stop(); 327 | connectionStatus = SocketConnectionStatus.CONNECTED; 328 | if (isReconnecting()) 329 | dispatchEvent(new SocketEvent(SocketEvent.RECONNECT)); 330 | else 331 | dispatchEvent(new SocketEvent(SocketEvent.CONNECT)); 332 | } 333 | } 334 | case 2: // heartbeat 335 | { 336 | _proxy.sendMessage("2::"); 337 | } 338 | case 3: // message 339 | { 340 | dispatchEvent(new SocketEvent(SocketEvent.MESSAGE, message.data)); 341 | } 342 | case 4: // Json message 343 | { 344 | var data : Dynamic = null; 345 | try 346 | { 347 | data = Json.parse(message.data); 348 | } 349 | catch (unknown : Dynamic) 350 | { 351 | data = null; 352 | } 353 | if (data != null) 354 | dispatchEvent(new SocketEvent(SocketEvent.MESSAGE, data)); 355 | } 356 | case 5: // Event 357 | { 358 | var data : Dynamic = null; 359 | try 360 | { 361 | data = Json.parse(message.data); 362 | } 363 | catch (unknown : Dynamic) 364 | { 365 | data = null; 366 | } 367 | if (data != null) 368 | dispatchEvent(new SocketEvent(data.name, data.args)); 369 | } 370 | case 6: // ACK 371 | { 372 | var ack : String = message.data; 373 | var args : Dynamic = null; 374 | var plus = message.data.indexOf("+"); 375 | if (plus != -1) 376 | { 377 | ack = Utils.Utf8Substr(message.data, 0, plus); 378 | var data = Utils.Utf8Substr(message.data, plus + 1, Utf8.length(message.data) - plus - 1); 379 | try 380 | { 381 | args = Json.parse(data); 382 | } 383 | catch (unknown : Dynamic) 384 | { 385 | } 386 | } 387 | if (_callbacks.exists(ack)) 388 | { 389 | var func = _callbacks.get(ack); 390 | _callbacks.remove(ack); 391 | func(args); 392 | } 393 | } 394 | case 7: // Error 395 | { 396 | var plus = message.data.indexOf("+"); 397 | if (plus != -1) 398 | { 399 | var reasonString = Utils.Utf8Substr(message.data, 0, plus); 400 | var adviceString = Utils.Utf8Substr(message.data, plus + 1, Utf8.length(message.data) - plus - 1); 401 | dispatchEvent(new SocketEvent(SocketEvent.ERROR, {reason : reasonString, advice : adviceString})); 402 | } 403 | } 404 | } 405 | } 406 | 407 | private function onReconnect(event : TimerEvent) : Void 408 | { 409 | _reconnectTimer.reset(); 410 | _reconnectionAttemptsLeft--; 411 | connect(); 412 | } 413 | 414 | private function onConnectTimeout(event : TimerEvent) : Void 415 | { 416 | _connectTimer.reset(); 417 | _proxy.removeEventListener(endpoint, onMessage); 418 | SocketProxy.disconnectSocket(this); 419 | _proxy = null; 420 | tryNextTransport(); 421 | } 422 | 423 | private function tryNextTransport() 424 | { 425 | _currentTransports.shift(); 426 | if (_currentTransports.length > 0) 427 | { 428 | connectAttempt(); 429 | return; 430 | } 431 | connectionStatus = SocketConnectionStatus.DISCONNECTED; 432 | if (isReconnecting()) 433 | { 434 | if (_reconnectionAttemptsLeft == 0) 435 | dispatchEvent(new SocketEvent(SocketEvent.RECONNECT_FAILED)); 436 | else 437 | _reconnectTimer.start(); 438 | } 439 | else 440 | dispatchEvent(new SocketEvent(SocketEvent.CONNECT_FAILED)); 441 | } 442 | 443 | private var _uri : String; 444 | private var _buffer : Array; 445 | private var _reconnectionAttemptsLeft : Int; 446 | private var _proxy : SocketProxy; 447 | private var _ack : Int; 448 | private var _callbacks : Map Void > ; 449 | private var _connectTimer : Timer; 450 | private var _reconnectTimer : Timer; 451 | private var _transports : Array; 452 | private var _currentTransports : Array; 453 | 454 | // Options default values 455 | private var _connectTimeout : Int = 10000; // ms 456 | private var _reconnect : Bool = true; 457 | private var _maxReconnectionAttempts : Int = 10; 458 | private var _reconnectionDelay : Int = 500; // ms 459 | private var _flashPolicyPort : Int = 843; 460 | private var _flashPolicyUrl : String = null; 461 | } -------------------------------------------------------------------------------- /Extension/com/gemioli/io/net/WebSocket.hx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013, Dmitriy Kapustin (dimanux), gemioli.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.gemioli.io.net; 24 | 25 | #if js 26 | 27 | @:native("WebSocket") 28 | extern class WebSocket 29 | { 30 | public inline static var CONNECTING : Int = 0; 31 | public inline static var OPEN : Int = 1; 32 | public inline static var CLOSING : Int = 2; 33 | public inline static var CLOSED : Int = 3; 34 | 35 | static function __init__() : Void 36 | { 37 | haxe.macro.Compiler.includeFile("com/gemioli/io/net/WebSocket.js"); 38 | } 39 | 40 | public var url(default, null) : String; 41 | public var readyState(default, null) : Int; 42 | public var extensions(default, null) : String; 43 | public var protocol(default, null) : String; 44 | 45 | public function new(url : String, ?protocols : Dynamic) : Void; 46 | 47 | public function send(data : Dynamic) : Void; 48 | public function close(?code : Int, ?reason : String) : Void; 49 | 50 | public var onopen : Dynamic->Void; 51 | public var onmessage : Dynamic->Void; 52 | public var onclose : Dynamic->Void; 53 | public var onerror : Dynamic->Void; 54 | } 55 | #else 56 | 57 | import com.gemioli.io.net.events.CloseEvent; 58 | import com.gemioli.io.net.events.MessageEvent; 59 | import com.gemioli.io.utils.BaseCode64; 60 | import com.gemioli.io.utils.URLParser; 61 | import com.gemioli.io.net.events.ErrorEvent; 62 | 63 | #if haxe3 64 | import com.gemioli.io.utils.Int32; 65 | #end 66 | 67 | import haxe.io.Eof; 68 | 69 | #if (haxe_211 || haxe3) 70 | import haxe.crypto.Sha1; 71 | #else 72 | import haxe.SHA1; 73 | #end 74 | 75 | #if openfl 76 | import flash.events.Event; 77 | import flash.events.EventDispatcher; 78 | import flash.Lib; 79 | import flash.utils.ByteArray; 80 | import flash.events.ProgressEvent; 81 | import flash.events.IOErrorEvent; 82 | import flash.events.SecurityErrorEvent; 83 | #else 84 | import nme.events.Event; 85 | import nme.events.EventDispatcher; 86 | import nme.Lib; 87 | import nme.utils.ByteArray; 88 | import nme.events.ProgressEvent; 89 | import nme.events.IOErrorEvent; 90 | import nme.events.SecurityErrorEvent; 91 | #end 92 | 93 | 94 | 95 | 96 | #if flash 97 | import flash.net.Socket; 98 | #else // cpp 99 | import sys.net.Host; 100 | //import cpp.vm.Thread; 101 | import haxe.io.Error; 102 | 103 | private class Socket extends EventDispatcher 104 | { 105 | public function new() 106 | { 107 | super(); 108 | _socket = new sys.net.Socket(); 109 | _running = false; 110 | } 111 | 112 | public function connect(host : String, port : Int) : Void 113 | { 114 | try 115 | { 116 | _socket.connect(new Host(host), port); 117 | _socket.setBlocking(false); 118 | Lib.current.stage.addEventListener(Event.ENTER_FRAME, socketLoop); 119 | } 120 | catch (e : Dynamic) 121 | { 122 | dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "Socket can't connect to host[" + host + ":" + port + "]")); 123 | return; 124 | } 125 | _running = true; 126 | dispatchEvent(new Event(Event.CONNECT)); 127 | } 128 | 129 | public function close() : Void 130 | { 131 | if (!_running) 132 | { 133 | dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "Close failed - socket is not opened.")); 134 | return; 135 | } 136 | Lib.current.stage.removeEventListener(Event.ENTER_FRAME, socketLoop); 137 | _socket.close(); 138 | _running = false; 139 | dispatchEvent(new Event(Event.CLOSE)); 140 | } 141 | 142 | public function writeBytes(bytes : ByteArray, offset : Int = 0, length : Int = 0) : Void 143 | { 144 | try 145 | { 146 | if (!_running) 147 | { 148 | dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "Can't write bytes to socket - socket is closed.")); 149 | return; 150 | } 151 | _socket.output.writeBytes(bytes, offset, length == 0 ? bytes.length - offset : length); 152 | } 153 | catch (e : Dynamic) 154 | { 155 | dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "Can't write bytes to socket.")); 156 | } 157 | } 158 | 159 | public function writeUTFBytes(value : String) : Void 160 | { 161 | try 162 | { 163 | if (!_running) 164 | { 165 | dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "Can't write UTFBytes to socket - socket is closed.")); 166 | return; 167 | } 168 | _socket.output.writeString(value); 169 | } 170 | catch (e : Dynamic) 171 | { 172 | dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "Can't write UTFBytes to socket.")); 173 | } 174 | } 175 | 176 | public function flush() : Void 177 | { 178 | try 179 | { 180 | if (!_running) 181 | { 182 | dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "Can't flush socket - socket is closed.")); 183 | return; 184 | } 185 | _socket.output.flush(); 186 | } 187 | catch (e : Dynamic) 188 | { 189 | dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "Can't flush socket.")); 190 | } 191 | } 192 | 193 | public function readBytes(bytes : ByteArray) : Void 194 | { 195 | try 196 | { 197 | if (!_running) 198 | { 199 | dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "Can't read bytes from socket - socket is closed.")); 200 | return; 201 | } 202 | var buf = new ByteArray(1 << 14); 203 | while (true) 204 | { 205 | try 206 | { 207 | var length = _socket.input.readBytes(buf, 0, 1 << 14); 208 | bytes.writeBytes(buf, 0, length); 209 | } 210 | catch (e : Eof) 211 | { 212 | close(); 213 | break; 214 | } 215 | catch (e : Error) 216 | { 217 | if (e == Blocked) 218 | break; 219 | else 220 | throw e; 221 | } 222 | } 223 | } 224 | catch (e : Dynamic) 225 | { 226 | dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, "Can't read bytes from socket.")); 227 | } 228 | } 229 | 230 | private function socketLoop(event : Event) : Void 231 | { 232 | dispatchEvent(new ProgressEvent(ProgressEvent.SOCKET_DATA)); 233 | } 234 | 235 | private var _socket : sys.net.Socket; 236 | private var _running : Bool; 237 | } 238 | #end 239 | 240 | // Thanks to gimite (https://github.com/gimite/web-socket-js) 241 | class WebSocket extends EventDispatcher 242 | { 243 | public inline static var CONNECTING : Int = 0; 244 | public inline static var OPEN : Int = 1; 245 | public inline static var CLOSING : Int = 2; 246 | public inline static var CLOSED : Int = 3; 247 | 248 | public var url(default, null) : String; 249 | public var readyState(default, null) : Int; 250 | public var extensions(default, null) : String; 251 | public var protocol(default, null) : String; 252 | 253 | public function new(url : String, ?protocols : Dynamic) 254 | { 255 | super(); 256 | this.url = url; 257 | _protocols = new Array(); 258 | if (Std.is(protocols, String)) 259 | _protocols.push(protocols); 260 | else if (Std.is(protocols, Array)) 261 | _protocols = _protocols.concat(protocols); 262 | _uri = URLParser.parse(url); 263 | _socket = new Socket(); 264 | _socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData); 265 | _socket.addEventListener(Event.CONNECT, onSocketConnect); 266 | _socket.addEventListener(Event.CLOSE, onSocketClose); 267 | _socket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIOError); 268 | _socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError); 269 | _buffer = new ByteArray(); 270 | _framesQueue = new Array(); 271 | _framesPayloadLength = Int32.ofInt(0); 272 | readyState = CLOSED; 273 | addEventListener(Event.OPEN, onOpen); 274 | addEventListener(Event.CLOSE, onClose); 275 | addEventListener(ErrorEvent.ERROR, onError); 276 | addEventListener(MessageEvent.MESSAGE, onMessage); 277 | } 278 | 279 | public function connect() : Void 280 | { 281 | if (readyState != CLOSED) 282 | { 283 | dispatchEvent(new ErrorEvent("Can't connect WebSocket - WebSocket not closed.")); 284 | return; 285 | } 286 | readyState = CONNECTING; 287 | _socket.connect(_uri.host, Std.parseInt(_uri.port)); 288 | } 289 | 290 | public function send(data : Dynamic) : Void 291 | { 292 | if (Std.is(data, String)) 293 | sendFrame(WebSocketFrame.textFrame(cast(data, String))); 294 | else if (Std.is(data, ByteArray)) 295 | sendFrame(WebSocketFrame.binaryFrame(cast(data, ByteArray))); 296 | else 297 | dispatchEvent(new ErrorEvent("Can't send data of unknown type - String and ByteArray only.")); 298 | } 299 | 300 | public function close(?code : Int = CloseEvent.CLOSE_NO_STATUS, ?reason : String = "No reason.") : Void 301 | { 302 | if (readyState == CLOSED) 303 | { 304 | dispatchEvent(new ErrorEvent("Can't close WebSocket - WebSocket already closed.")); 305 | return; 306 | } 307 | if (code == CloseEvent.CLOSE_ABNORMAL || readyState == CONNECTING) 308 | { 309 | closeSocket(code, reason, false); 310 | return; 311 | } 312 | if (readyState == OPEN) 313 | { 314 | readyState = CLOSING; 315 | sendFrame(WebSocketFrame.closeFrame(code, reason)); 316 | closeSocket(code, reason, true); 317 | } 318 | } 319 | 320 | public var onopen : Dynamic->Void; 321 | public var onmessage : Dynamic->Void; 322 | public var onclose : Dynamic->Void; 323 | public var onerror : Dynamic->Void; 324 | 325 | private function closeSocket(code : Int, reason : String, wasClean : Bool) : Void 326 | { 327 | if (readyState == CLOSED) 328 | return; 329 | readyState = CLOSED; 330 | _buffer = new ByteArray(); 331 | _framesQueue = new Array(); 332 | _framesPayloadLength = Int32.ofInt(0); 333 | try 334 | { 335 | _socket.close(); 336 | } 337 | catch (e : Dynamic) 338 | { 339 | // Do nothing 340 | } 341 | dispatchEvent(new CloseEvent(code, reason, wasClean)); 342 | } 343 | 344 | private function sendFrame(frame : WebSocketFrame) : Void 345 | { 346 | if (readyState != OPEN && frame.opcode != 0x8) 347 | { 348 | dispatchEvent(new ErrorEvent("Can't send data while socket is not opened.")); 349 | return; 350 | } 351 | #if flash 352 | var mask = new ByteArray(); 353 | mask.length = 4; 354 | #else 355 | var mask = new ByteArray(4); 356 | #end 357 | for (i in 0...mask.length) 358 | mask[i] = Std.random(256); 359 | 360 | var payloadLength = frame.payload.length; 361 | var data = new ByteArray(); 362 | data.writeByte((frame.fin ? 0x80 : 0x00) | (frame.rsv << 4) | frame.opcode); 363 | if (payloadLength < 126) 364 | data.writeByte(0x80 | payloadLength); 365 | else if (payloadLength < 65536) 366 | { 367 | data.writeByte(0x80 | 126); 368 | data.writeShort(payloadLength); 369 | } 370 | else if (payloadLength <= 4294967295) 371 | { 372 | data.writeByte(0x80 | 127); 373 | data.writeUnsignedInt(0); 374 | data.writeUnsignedInt(payloadLength); 375 | } 376 | else 377 | dispatchEvent(new ErrorEvent("Sended data is too long.")); 378 | data.writeBytes(mask); 379 | 380 | #if flash 381 | var payload = new ByteArray(); 382 | payload.length = payloadLength; 383 | #else 384 | var payload = new ByteArray(payloadLength); 385 | #end 386 | for (i in 0...payloadLength) 387 | payload[i] = frame.payload[i] ^ mask[i % 4]; 388 | 389 | data.writeBytes(payload, 0, payloadLength); 390 | 391 | try 392 | { 393 | _socket.writeBytes(data); 394 | _socket.flush(); 395 | } 396 | catch (e : Dynamic) 397 | { 398 | close(CloseEvent.CLOSE_ABNORMAL, "Socket sending error."); 399 | } 400 | } 401 | 402 | private function onSocketData(event : ProgressEvent) : Void 403 | { 404 | if (readyState == CLOSED) 405 | return; 406 | 407 | try 408 | { 409 | _socket.readBytes(_buffer); 410 | } 411 | catch (e : Dynamic) 412 | { 413 | close(CloseEvent.CLOSE_ABNORMAL, "Socket receiving data error."); 414 | } 415 | 416 | if (readyState == CONNECTING) 417 | { 418 | // Read handshake 419 | var headersDelimeter = _buffer.toString().indexOf("\r\n\r\n"); 420 | if (headersDelimeter >= 0) 421 | { 422 | _buffer.position = 0; 423 | var headersArray : Array = _buffer.readUTFBytes(headersDelimeter).split("\r\n"); 424 | var newBuffer = new ByteArray(); 425 | _buffer.readUTFBytes(4); // pass "\r\n\r\n" 426 | _buffer.readBytes(newBuffer); 427 | _buffer = newBuffer; 428 | 429 | if (headersArray.length == 0 || headersArray[0].substr(0, 12) != "HTTP/1.1 101") 430 | { 431 | close(CloseEvent.CLOSE_ABNORMAL, "Bad response: " + (headersArray.length == 0 ? "No headers." : headersArray[0])); 432 | return; 433 | } 434 | else 435 | headersArray.shift(); 436 | 437 | var headers = new Map(); 438 | for (headerString in headersArray) 439 | { 440 | var delim = headerString.indexOf(":"); 441 | if (delim == -1) 442 | { 443 | close(CloseEvent.CLOSE_ABNORMAL, "Bad header: " + headerString); 444 | return; 445 | } 446 | var name = StringTools.trim(headerString.substr(0, delim).toLowerCase()); 447 | var value = StringTools.trim(headerString.substr(delim + 1)); 448 | headers.set(name, value); 449 | } 450 | 451 | if (headers.get("upgrade").toLowerCase() != "websocket") 452 | { 453 | close(CloseEvent.CLOSE_ABNORMAL, "Bad upgrade header: " + headers.get("upgrade")); 454 | return; 455 | } 456 | 457 | if (headers.get("connection").toLowerCase() != "upgrade") 458 | { 459 | close(CloseEvent.CLOSE_ABNORMAL, "Bad connection header: " + headers.get("connection")); 460 | return; 461 | } 462 | 463 | var requestedKey = headers.get("sec-websocket-accept"); 464 | #if neko 465 | if (requestedKey != null){ 466 | requestedKey = requestedKey.substr(0, requestedKey.length-2); 467 | _expectedKey = _expectedKey.substr(0, requestedKey.length); 468 | } 469 | 470 | #end 471 | if (requestedKey != _expectedKey) 472 | { 473 | close(CloseEvent.CLOSE_ABNORMAL, "Key [" + headers.get("sec-websocket-accept") + "] not equals to expected [" + _expectedKey + "]."); 474 | return; 475 | } 476 | 477 | if (_protocols.length > 0) 478 | { 479 | protocol = headers.get("sec-websocket-protocol"); 480 | if (!Lambda.has(_protocols, protocol)) 481 | { 482 | close(CloseEvent.CLOSE_ABNORMAL, "Server protocol [" + headers.get("sec-websocket-protocol") + "] not equals to exprected protocols [" + _protocols.join(",") + "]."); 483 | return; 484 | } 485 | } 486 | 487 | readyState = OPEN; 488 | dispatchEvent(new Event(Event.OPEN)); 489 | parseFrames(); 490 | } 491 | } 492 | else 493 | { 494 | parseFrames(); 495 | } 496 | } 497 | 498 | private function parseFrames() : Void 499 | { 500 | while (WebSocketFrame.isFrameReady(_buffer)) 501 | { 502 | var frame = WebSocketFrame.readFrame(_buffer); 503 | var newBuffer = new ByteArray(); 504 | _buffer.readBytes(newBuffer); 505 | _buffer = newBuffer; 506 | if (frame.rsv != 0) 507 | close(CloseEvent.CLOSE_PROTOCOL_ERROR, "RSV must be 0."); 508 | else if (frame.mask) 509 | close(CloseEvent.CLOSE_PROTOCOL_ERROR, "Get masked frame from server."); 510 | else if (frame.overflow) 511 | close(CloseEvent.CLOSE_TOO_LARGE, "Frame length is too big."); 512 | else if (frame.opcode >= 0x08 && frame.opcode <= 0x0f && frame.payload.length >= 126) 513 | close(CloseEvent.CLOSE_TOO_LARGE, "Payload length of control frame more than 125 bytes."); 514 | else 515 | { 516 | switch (frame.opcode) 517 | { 518 | case 0x0: // Continuation frame 519 | { 520 | if (_framesQueue.length == 0) 521 | { 522 | close(CloseEvent.CLOSE_PROTOCOL_ERROR, "Received unexpected continuation frame."); 523 | continue; 524 | } 525 | _framesPayloadLength = Int32.add(_framesPayloadLength, Int32.ofInt(frame.payload.length)); 526 | try 527 | { 528 | var messageLength = Int32.toInt(_framesPayloadLength); 529 | } 530 | catch (e : Dynamic) 531 | { 532 | // Overflow 533 | close(CloseEvent.CLOSE_TOO_LARGE, "Received message is too big."); 534 | _framesQueue.splice(0, _framesQueue.length); 535 | _framesPayloadLength = Int32.ofInt(0); 536 | continue; 537 | } 538 | _framesQueue.push(frame); 539 | if (frame.fin) 540 | { 541 | if (readyState == OPEN) // Dispatch Messages only in OPEN state 542 | { 543 | var payload = new ByteArray(); 544 | for (queueFrame in _framesQueue) 545 | queueFrame.payload.readBytes(payload); 546 | switch (_framesQueue[0].opcode) 547 | { 548 | case 0x1: // Text frame 549 | { 550 | dispatchEvent(new MessageEvent(payload.readMultiByte(payload.length, "utf-8"))); 551 | } 552 | case 0x2: // Binary frame 553 | { 554 | dispatchEvent(new MessageEvent(payload)); 555 | } 556 | } 557 | } 558 | _framesQueue.splice(0, _framesQueue.length); 559 | _framesPayloadLength = Int32.ofInt(0); 560 | } 561 | } 562 | case 0x1: // Text frame 563 | { 564 | if (_framesQueue.length != 0) 565 | { 566 | close(CloseEvent.CLOSE_PROTOCOL_ERROR, "Received Text Frame during continuation."); 567 | continue; 568 | } 569 | if (frame.fin && (readyState == OPEN)) 570 | { 571 | dispatchEvent(new MessageEvent(frame.payload.readMultiByte(frame.payload.length, "utf-8"))); 572 | } 573 | else 574 | _framesQueue.push(frame); 575 | } 576 | case 0x2: // Binary frame 577 | { 578 | if (_framesQueue.length != 0) 579 | { 580 | close(CloseEvent.CLOSE_PROTOCOL_ERROR, "Received Binary Frame during continuation."); 581 | continue; 582 | } 583 | if (frame.fin && (readyState == OPEN)) 584 | { 585 | dispatchEvent(new MessageEvent(frame.payload)); 586 | } 587 | else 588 | _framesQueue.push(frame); 589 | } 590 | case 0x8: // Close frame 591 | { 592 | var code = CloseEvent.CLOSE_NO_STATUS; 593 | var reason = ""; 594 | if (frame.payload.length >= 2) 595 | { 596 | code = frame.payload.readUnsignedShort(); 597 | reason = frame.payload.readMultiByte(frame.payload.bytesAvailable, "utf-8"); 598 | } 599 | 600 | if (readyState == CLOSING) 601 | { 602 | closeSocket(code, reason, true); 603 | } 604 | else 605 | { 606 | close(code, reason); 607 | if (readyState != CLOSED) 608 | closeSocket(code, reason, true); 609 | } 610 | } 611 | case 0x9: // Ping 612 | { 613 | sendFrame(WebSocketFrame.pongFrame(frame.payload)); 614 | } 615 | case 0xA: // Pong 616 | { 617 | 618 | } 619 | default: 620 | close(CloseEvent.CLOSE_PROTOCOL_ERROR, "Received unknown opcode[" + frame.opcode + "]."); 621 | } 622 | } 623 | } 624 | } 625 | 626 | private function onSocketConnect(event : Event) : Void 627 | { 628 | if (readyState == CLOSED) 629 | return; 630 | #if flash 631 | var requestBytes = new ByteArray(); 632 | requestBytes.length = 16; 633 | #else 634 | var requestBytes = new ByteArray(16); 635 | #end 636 | for (i in 0...requestBytes.length) 637 | requestBytes[i] = Std.random(256); 638 | var requestKey = BaseCode64.encodeByteArray(requestBytes); 639 | #if !haxe3 640 | var shaKey = SHA1.encode(requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); 641 | #else 642 | var shaKey = Sha1.encode(requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); 643 | #end 644 | requestBytes.clear(); 645 | for (i in 0...Std.int(shaKey.length / 2)) 646 | requestBytes.writeByte(Std.parseInt("0x" + shaKey.substr(i * 2, 2))); 647 | _expectedKey = BaseCode64.encodeByteArray(requestBytes); 648 | var queryPart = if (_uri.query.length > 0) '?'+_uri.query else ''; 649 | var request = "GET " + _uri.path + queryPart + " HTTP/1.1\r\n" + 650 | "Host: " + _uri.host + (_uri.port == "" ? "" : (":" + _uri.port)) + "\r\n" + 651 | "Upgrade: websocket\r\n" + 652 | "Connection: Upgrade\r\n" + 653 | "Sec-WebSocket-Key: " + requestKey + "\r\n" + 654 | "Origin: " + "socket.io-nme-client" + "\r\n" + // TODO: Get origin from host 655 | "Sec-WebSocket-Version: 13\r\n"; 656 | if (_protocols.length > 0) 657 | request += "Sec-WebSocket-Protocol: " + _protocols.join(",") + "\r\n"; 658 | request += "\r\n"; 659 | try 660 | { 661 | _socket.writeUTFBytes(request); 662 | _socket.flush(); 663 | } 664 | catch (e : Dynamic) 665 | { 666 | close(CloseEvent.CLOSE_ABNORMAL, "Socket sending handshake error."); 667 | } 668 | } 669 | 670 | private function onSocketClose(event : Event) : Void 671 | { 672 | if (readyState != CLOSED) 673 | { 674 | readyState = CLOSED; 675 | dispatchEvent(new CloseEvent(CloseEvent.CLOSE_ABNORMAL, "Closed without close handshake.", false)); 676 | } 677 | } 678 | 679 | private function onSocketIOError(event : IOErrorEvent) : Void 680 | { 681 | if (readyState != CLOSED) 682 | close(CloseEvent.CLOSE_ABNORMAL, readyState == CONNECTING ? 683 | "Can't connect to url[" + url + "]. IOError [" + event.text + "]." : 684 | "Error communicating with url[" + url + "]. IOError [" + event.text + "]."); 685 | } 686 | 687 | private function onSocketSecurityError(event : SecurityErrorEvent) : Void 688 | { 689 | if (readyState != CLOSED) 690 | { 691 | close(CloseEvent.CLOSE_ABNORMAL, readyState == CONNECTING ? 692 | "Can't connect to url[" + url + "]. SecurityError [" + event.text + "]." : 693 | "Error communicating with url[" + url + "]. SecurityError [" + event.text + "]."); 694 | } 695 | } 696 | 697 | private function onOpen(event : Event) : Void 698 | { 699 | if (onopen != null) 700 | onopen(event); 701 | } 702 | 703 | private function onClose(event : CloseEvent) : Void 704 | { 705 | if (onclose != null) 706 | onclose(event); 707 | } 708 | 709 | private function onMessage(event : MessageEvent) : Void 710 | { 711 | if (onmessage != null) 712 | onmessage(event); 713 | } 714 | 715 | private function onError(event : ErrorEvent) : Void 716 | { 717 | if (onerror != null) 718 | onerror(event); 719 | } 720 | 721 | private var _protocols : Array; 722 | private var _uri : URLParser; 723 | private var _socket : Socket; 724 | private var _buffer : ByteArray; 725 | private var _expectedKey : String; 726 | private var _framesQueue : Array; 727 | private var _framesPayloadLength : Int32; 728 | } 729 | 730 | private class WebSocketFrame 731 | { 732 | public var fin : Bool; 733 | public var rsv : Int; 734 | public var opcode : Int; 735 | public var mask : Bool; 736 | public var overflow : Bool; // Data overflow 737 | public var payload : ByteArray; 738 | 739 | private function new() 740 | { 741 | fin = true; 742 | rsv = 0; 743 | opcode = 0; 744 | mask = true; 745 | overflow = false; 746 | } 747 | 748 | public static function readFrame(buffer : ByteArray) : WebSocketFrame 749 | { 750 | var frame = new WebSocketFrame(); 751 | frame.fin = (buffer[0] & 0x80) != 0; 752 | frame.rsv = (buffer[0] & 0x70) >> 4; 753 | frame.opcode = buffer[0] & 0x0f; 754 | frame.mask = (buffer[1] & 0x80) != 0; 755 | var payloadLength = buffer[1] & 0x7f; 756 | buffer.position = 2; 757 | if (payloadLength == 126) 758 | { 759 | payloadLength = buffer.readUnsignedShort(); 760 | } 761 | else if (payloadLength == 127) 762 | { 763 | var bigLength = buffer.readUnsignedInt(); 764 | if (bigLength != 0) 765 | frame.overflow = true; 766 | payloadLength = buffer.readUnsignedInt(); 767 | } 768 | frame.payload = new ByteArray(); 769 | buffer.readBytes(frame.payload, 0, payloadLength); 770 | return frame; 771 | } 772 | 773 | public static function isFrameReady(buffer : ByteArray) : Bool 774 | { 775 | var headersLength : Int = 2; // Min length 776 | if (cast(buffer.length, Int) < headersLength) 777 | return false; 778 | var payloadLength : Int = buffer[1] & 0x7f; 779 | if (payloadLength == 126) 780 | { 781 | headersLength = 4; 782 | if (cast(buffer.length, Int) < headersLength) 783 | return false; 784 | buffer.position = 2; 785 | payloadLength = buffer.readUnsignedShort(); 786 | } 787 | else if (payloadLength == 127) 788 | { 789 | headersLength = 10; 790 | if (cast(buffer.length, Int) < headersLength) 791 | return false; 792 | buffer.position = 2; 793 | buffer.readUnsignedInt(); 794 | payloadLength = buffer.readUnsignedInt(); 795 | } 796 | if (cast(buffer.length, Int) < headersLength + payloadLength) 797 | return false; 798 | return true; 799 | } 800 | 801 | public static function textFrame(text : String) : WebSocketFrame 802 | { 803 | var frame = new WebSocketFrame(); 804 | frame.opcode = 0x1; 805 | frame.payload = new ByteArray(); 806 | frame.payload.writeUTFBytes(text); 807 | return frame; 808 | } 809 | 810 | public static function binaryFrame(data : ByteArray) : WebSocketFrame 811 | { 812 | var frame = new WebSocketFrame(); 813 | frame.opcode = 0x2; 814 | frame.payload = new ByteArray(); 815 | frame.payload.writeBytes(data); 816 | return frame; 817 | } 818 | 819 | public static function pongFrame(ping : ByteArray) : WebSocketFrame 820 | { 821 | var frame = new WebSocketFrame(); 822 | frame.opcode = 0xA; 823 | frame.payload = ping; 824 | return frame; 825 | } 826 | 827 | public static function closeFrame(code : Int, reason : String) : WebSocketFrame 828 | { 829 | var frame = new WebSocketFrame(); 830 | frame.opcode = 0x8; 831 | frame.payload = new ByteArray(); 832 | if (code != CloseEvent.CLOSE_NO_STATUS) 833 | { 834 | frame.payload.writeShort(code); 835 | frame.payload.writeUTFBytes(reason); 836 | } 837 | return frame; 838 | } 839 | } 840 | 841 | #end --------------------------------------------------------------------------------