Skip to content

Commit

Permalink
Switching to single event "event" instead of "open", "message", etc. …
Browse files Browse the repository at this point in the history
…in Flash interface, to keep order of all events and avoid recursive call error.

Using setTimeout() for event callback to avoid recursive call error.
Removing FABridge.EventsToCallLater. Hopefully setTimeout() above is more confident alternative.
  • Loading branch information
gimite committed Jan 11, 2011
1 parent 6640d9d commit 20f8374
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 156 deletions.
Binary file modified WebSocketMain.swf
Binary file not shown.
Binary file modified WebSocketMainInsecure.zip
Binary file not shown.
61 changes: 20 additions & 41 deletions flash-src/WebSocket.as
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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");

Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand All @@ -277,27 +256,23 @@ 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
main.log("received closing packet");
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;
}

Expand Down Expand Up @@ -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 {
Expand Down
10 changes: 0 additions & 10 deletions flash-src/WebSocketMain.as
Expand Up @@ -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 {
Expand Down
32 changes: 0 additions & 32 deletions flash-src/WebSocketStateEvent.as

This file was deleted.

126 changes: 53 additions & 73 deletions web_socket.js
Expand Up @@ -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";
}
Expand All @@ -113,15 +62,14 @@
if (result < 0) { // success
return true;
} else {
this.bufferedAmount = result;
this.bufferedAmount += result;
return false;
}
};

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
Expand All @@ -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);
}
};

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 20f8374

Please sign in to comment.