diff --git a/WebSocketMain.swf b/WebSocketMain.swf index 575591d..694f9dc 100644 Binary files a/WebSocketMain.swf and b/WebSocketMain.swf differ diff --git a/WebSocketMainInsecure.zip b/WebSocketMainInsecure.zip index 8ce4aef..ad3c9b9 100644 Binary files a/WebSocketMainInsecure.zip and b/WebSocketMainInsecure.zip differ diff --git a/flash-src/WebSocket.as b/flash-src/WebSocket.as index 40970ed..0733570 100644 --- a/flash-src/WebSocket.as +++ b/flash-src/WebSocket.as @@ -22,11 +22,7 @@ import com.hurlant.crypto.tls.TLSEngine; import com.hurlant.crypto.tls.TLSSecurityParameters; import com.gsolo.encryption.MD5; -[Event(name="message", type="flash.events.Event")] -[Event(name="open", type="flash.events.Event")] -[Event(name="close", type="flash.events.Event")] -[Event(name="error", type="flash.events.Event")] -[Event(name="stateChange", type="WebSocketStateEvent")] +[Event(name="event", type="flash.events.Event")] public class WebSocket extends EventDispatcher { private static var CONNECTING:int = 0; @@ -47,10 +43,9 @@ public class WebSocket extends EventDispatcher { private var origin:String; private var protocol:String; private var buffer:ByteArray = new ByteArray(); - private var dataQueue:Array; + private var eventQueue:Array = []; private var headerState:int = 0; private var readyState:int = CONNECTING; - private var bufferedAmount:int = 0; private var headers:String; private var noiseChars:Array; private var expectedDigest:String; @@ -115,23 +110,19 @@ public class WebSocket extends EventDispatcher { socket.flush(); main.log("sent: " + data); return -1; - } else if (readyState == CLOSED) { + } else if (readyState == CLOSING || readyState == CLOSED) { var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(data); - bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff - // We use return value to let caller know bufferedAmount because we cannot fire - // stateChange event here which causes weird error: - // > You are trying to call recursively into the Flash Player which is not allowed. - return bufferedAmount; + return bytes.length; // not sure whether it should include \x00 and \xff } else { - main.fatal("INVALID_STATE_ERR: invalid state"); + main.fatal("invalid state"); return 0; } } public function close():void { main.log("close"); - dataQueue = []; + eventQueue = []; try { if (readyState == OPEN) { socket.writeByte(0xff); @@ -146,14 +137,6 @@ public class WebSocket extends EventDispatcher { // We do something equivalent in JavaScript WebSocket#close instead. } - public function getReadyState():int { - return readyState; - } - - public function getBufferedAmount():int { - return bufferedAmount; - } - private function onSocketConnect(event:Event):void { main.log("connected"); @@ -162,7 +145,6 @@ public class WebSocket extends EventDispatcher { tlsSocket.startTLS(rawSocket, host, tlsConfig); } - dataQueue = []; var hostValue:String = host + (port == 80 ? "" : ":" + port); var cookie:String = ""; if (main.getCallerHost() == host) { @@ -199,8 +181,7 @@ public class WebSocket extends EventDispatcher { private function onSocketClose(event:Event):void { main.log("closed"); readyState = CLOSED; - notifyStateChange(); - dispatchEvent(new Event("close")); + fireEvent({type: "close"}, true); } private function onSocketIoError(event:IOErrorEvent):void { @@ -230,8 +211,7 @@ public class WebSocket extends EventDispatcher { if (state == CLOSED) return; main.error(message); close(); - notifyStateChange(); - dispatchEvent(new Event(state == CONNECTING ? "close" : "error")); + fireEvent({type: state == CONNECTING ? "close" : "error"}, true); } private function onSocketData(event:ProgressEvent):void { @@ -266,8 +246,7 @@ public class WebSocket extends EventDispatcher { removeBufferBefore(pos + 1); pos = -1; readyState = OPEN; - notifyStateChange(); - dispatchEvent(new Event("open")); + fireEvent({type: "open"}, true); } } else { if (buffer[pos] == 0xff && pos > 0) { @@ -277,8 +256,7 @@ public class WebSocket extends EventDispatcher { } var data:String = readUTFBytes(buffer, 1, pos - 1); main.log("received: " + data); - dataQueue.push(encodeURIComponent(data)); - dispatchEvent(new Event("message")); + fireEvent({type: "message", data: encodeURIComponent(data)}, false); removeBufferBefore(pos + 1); pos = -1; } else if (pos == 1 && buffer[0] == 0xff && buffer[1] == 0x00) { // closing @@ -286,18 +264,15 @@ public class WebSocket extends EventDispatcher { removeBufferBefore(pos + 1); pos = -1; close(); - notifyStateChange(); - dispatchEvent(new Event("close")); + fireEvent({type: "close"}, true); } } } } - public function readSocketData():Array { - var q:Array = dataQueue; - if (dataQueue.length > 0) { - dataQueue = []; - } + public function receiveEvents():Array { + var q:Array = eventQueue; + eventQueue = []; return q; } @@ -360,8 +335,12 @@ public class WebSocket extends EventDispatcher { buffer = nextBuffer; } - private function notifyStateChange():void { - dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount)); + private function fireEvent(event:Object, stateChanged:Boolean):void { + if (stateChanged) { + event.readyState = readyState; + } + eventQueue.push(event); + dispatchEvent(new Event("event")); } private function initNoiseChars():void { diff --git a/flash-src/WebSocketMain.as b/flash-src/WebSocketMain.as index dc7483a..9d6ff90 100644 --- a/flash-src/WebSocketMain.as +++ b/flash-src/WebSocketMain.as @@ -24,19 +24,9 @@ public class WebSocketMain extends Sprite { private var manualPolicyFileLoaded:Boolean = false; public function WebSocketMain() { - - // This is to avoid "You are trying to call recursively into the Flash Player ..." - // error which (I heard) happens when you pass bunch of messages. - // This workaround was written here: - // http://www.themorphicgroup.com/blog/2009/02/14/fabridge-error-you-are-trying-to-call-recursively-into-the-flash-player-which-is-not-allowed/ - FABridge.EventsToCallLater["flash.events::Event"] = "true"; - FABridge.EventsToCallLater["WebSocketMessageEvent"] = "true"; - FABridge.EventsToCallLater["WebSocketStateEvent"] = "true"; - var fab:FABridge = new FABridge(); fab.rootObject = this; //log("Flash initialized"); - } public function setCallerUrl(url:String):void { diff --git a/flash-src/WebSocketStateEvent.as b/flash-src/WebSocketStateEvent.as deleted file mode 100644 index c2af659..0000000 --- a/flash-src/WebSocketStateEvent.as +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright: Hiroshi Ichikawa -// License: New BSD License -// Reference: http://dev.w3.org/html5/websockets/ -// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31 - -package { - -import flash.display.*; -import flash.events.*; -import flash.external.*; -import flash.net.*; -import flash.system.*; -import flash.utils.*; -import mx.core.*; -import mx.controls.*; -import mx.events.*; -import mx.utils.*; - -public class WebSocketStateEvent extends Event { - - public var readyState:int; - public var bufferedAmount:int; - - public function WebSocketStateEvent(type:String, readyState:int, bufferedAmount:int) { - super(type); - this.readyState = readyState; - this.bufferedAmount = bufferedAmount; - } - -} - -} diff --git a/web_socket.js b/web_socket.js index baf3cf9..2a7da35 100644 --- a/web_socket.js +++ b/web_socket.js @@ -33,73 +33,22 @@ WebSocket.__addTask(function() { self.__createFlash(url, protocol, proxyHost, proxyPort, headers); }); - }, 1); + }, 0); }; WebSocket.prototype.__createFlash = function(url, protocol, proxyHost, proxyPort, headers) { var self = this; self.__flash = WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null); - - self.__flash.addEventListener("open", function(fe) { - try { - self.readyState = self.__flash.getReadyState(); - if (self.__timer) clearInterval(self.__timer); - if (window.opera) { - // Workaround for weird behavior of Opera which sometimes drops events. - self.__timer = setInterval(function () { - self.__handleMessages(); - }, 500); - } - if (self.onopen) self.onopen(); - } catch (e) { - console.error(e.toString()); - } - }); - - self.__flash.addEventListener("close", function(fe) { - try { - self.readyState = self.__flash.getReadyState(); - if (self.__timer) clearInterval(self.__timer); - if (self.onclose) self.onclose(); - } catch (e) { - console.error(e.toString()); - } - }); - - self.__flash.addEventListener("message", function() { - try { - self.__handleMessages(); - } catch (e) { - console.error(e.toString()); - } + self.__flash.addEventListener("event", function(fe) { + // Uses setTimeout() to workaround the error: + // > You are trying to call recursively into the Flash Player which is not allowed. + setTimeout(function() { self.__handleEvents(); }, 0); }); - - self.__flash.addEventListener("error", function(fe) { - try { - if (self.__timer) clearInterval(self.__timer); - if (self.onerror) self.onerror(); - } catch (e) { - console.error(e.toString()); - } - }); - - self.__flash.addEventListener("stateChange", function(fe) { - try { - self.readyState = self.__flash.getReadyState(); - self.bufferedAmount = fe.getBufferedAmount(); - } catch (e) { - console.error(e.toString()); - } - }); - //console.log("[WebSocket] Flash object is ready"); }; WebSocket.prototype.send = function(data) { - if (this.__flash) { - this.readyState = this.__flash.getReadyState(); - } if (!this.__flash || this.readyState == WebSocket.CONNECTING) { throw "INVALID_STATE_ERR: Web Socket connection has not been established"; } @@ -113,7 +62,7 @@ if (result < 0) { // success return true; } else { - this.bufferedAmount = result; + this.bufferedAmount += result; return false; } }; @@ -121,7 +70,6 @@ WebSocket.prototype.close = function() { var self = this; if (!self.__flash) return; - self.readyState = self.__flash.getReadyState(); if (self.readyState == WebSocket.CLOSED || self.readyState == WebSocket.CLOSING) return; self.__flash.close(); // Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events @@ -132,7 +80,7 @@ if (self.onclose) { // Make it asynchronous so that it looks more like an actual // close event - setTimeout(self.onclose, 1); + setTimeout(self.onclose, 0); } }; @@ -201,30 +149,62 @@ } }; - WebSocket.prototype.__handleMessages = function() { - // Gets data using readSocketData() instead of getting it from event object + WebSocket.prototype.__handleEvents = function() { + // Gets events using receiveEvents() instead of getting it from event object // of Flash event. This is to make sure to keep message order. // It seems sometimes Flash events don't arrive in the same order as they are sent. - var arr = this.__flash.readSocketData(); - for (var i = 0; i < arr.length; i++) { - var data = decodeURIComponent(arr[i]); + var events = this.__flash.receiveEvents(); + for (var i = 0; i < events.length; i++) { try { - if (this.onmessage) { - var e; - if (window.MessageEvent && !window.opera) { - e = document.createEvent("MessageEvent"); - e.initMessageEvent("message", false, false, data, null, null, window, null); - } else { // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes - e = {data: data}; + var event = events[i]; + if ("readyState" in event) { + this.readyState = event.readyState; + } + if (event.type == "open") { + + if (this.__timer) clearInterval(this.__timer); + if (window.opera) { + // Workaround for weird behavior of Opera which sometimes drops events. + this.__timer = setInterval(function () { + this.__handleEvents(); + }, 500); + } + if (this.onopen) this.onopen(); + + } else if (event.type == "close") { + + if (this.__timer) clearInterval(this.__timer); + if (this.onclose) this.onclose(); + + } else if (event.type == "message") { + + if (this.onmessage) { + var data = decodeURIComponent(event.data); + var e; + if (window.MessageEvent && !window.opera) { + e = document.createEvent("MessageEvent"); + e.initMessageEvent("message", false, false, data, null, null, window, null); + } else { + // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. + e = {data: data}; + } + this.onmessage(e); } - this.onmessage(e); + + } else if (event.type == "error") { + + if (this.__timer) clearInterval(this.__timer); + if (this.onerror) this.onerror(); + + } else { + throw "unknown event type: " + event.type; } } catch (e) { console.error(e.toString()); } } }; - + /** * @param {object} object * @param {string} type