├── 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 |
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 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------