Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of http://github.com/gimite/web-socket-js

Conflicts:
	WebSocketMain.swf
	WebSocketMainInsecure.zip
  • Loading branch information...
commit e9b99e680a03f7155ce3e66d20f6935000ff1d0f 2 parents 7772f79 + 20f8374
@Vagabond authored
View
BIN  WebSocketMain.swf
Binary file not shown
View
BIN  WebSocketMainInsecure.zip
Binary file not shown
View
87 flash-src/WebSocket.as
@@ -24,11 +24,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;
@@ -49,10 +45,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;
@@ -120,23 +115,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);
@@ -151,14 +142,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");
socketTimeout.stop();
@@ -168,7 +151,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) {
@@ -205,8 +187,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 {
@@ -236,8 +217,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 {
@@ -254,8 +234,7 @@ public class WebSocket extends EventDispatcher {
headerState = 0;
}
if (headerState == 4) {
- buffer.position = 0;
- var headerStr:String = buffer.readUTFBytes(pos + 1);
+ var headerStr:String = readUTFBytes(buffer, 0, pos + 1);
main.log("response header:\n" + headerStr);
if (!validateHeader(headerStr)) return;
removeBufferBefore(pos + 1);
@@ -263,8 +242,7 @@ public class WebSocket extends EventDispatcher {
}
} else if (headerState == 4) {
if (pos == 15) {
- buffer.position = 0;
- var replyDigest:String = readBytes(buffer, 16);
+ var replyDigest:String = readBytes(buffer, 0, 16);
main.log("reply digest: " + replyDigest);
if (replyDigest != expectedDigest) {
onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
@@ -274,8 +252,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) {
@@ -283,11 +260,9 @@ public class WebSocket extends EventDispatcher {
onError("data must start with \\x00");
return;
}
- buffer.position = 1;
- var data:String = buffer.readUTFBytes(pos - 1);
+ 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
@@ -295,18 +270,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;
}
@@ -369,8 +341,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 {
@@ -440,7 +416,8 @@ public class WebSocket extends EventDispatcher {
// Reads specified number of bytes from buffer, and returns it as special format String
// where bytes[i] is i-th byte (not i-th character).
- private function readBytes(buffer:ByteArray, numBytes:int):String {
+ private function readBytes(buffer:ByteArray, start:int, numBytes:int):String {
+ buffer.position = start;
var bytes:String = "";
for (var i:int = 0; i < numBytes; ++i) {
// & 0xff is to make \x80-\xff positive number.
@@ -449,6 +426,20 @@ public class WebSocket extends EventDispatcher {
return bytes;
}
+ private function readUTFBytes(buffer:ByteArray, start:int, numBytes:int):String {
+ buffer.position = start;
+ var data:String = "";
+ for(var i:int = start; i < start + numBytes; ++i) {
+ // Workaround of a bug of ByteArray#readUTFBytes() that bytes after "\x00" is discarded.
+ if (buffer[i] == 0x00) {
+ data += buffer.readUTFBytes(i - buffer.position) + "\x00";
+ buffer.position = i + 1;
+ }
+ }
+ data += buffer.readUTFBytes(start + numBytes - buffer.position);
+ return data;
+ }
+
private function randomInt(min:uint, max:uint):uint {
return min + Math.floor(Math.random() * (Number(max) - min + 1));
}
View
34 flash-src/WebSocketMain.as
@@ -19,24 +19,14 @@ import bridge.FABridge;
public class WebSocketMain extends Sprite {
- private var policyLoaded:Boolean = false;
private var callerUrl:String;
private var debug:Boolean = false;
+ 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 {
@@ -51,7 +41,9 @@ public class WebSocketMain extends Sprite {
url:String, protocol:String,
proxyHost:String = null, proxyPort:int = 0,
headers:String = null):WebSocket {
- loadPolicyFile(null);
+ if (!manualPolicyFileLoaded) {
+ loadDefaultPolicyFile(url);
+ }
return new WebSocket(this, url, protocol, proxyHost, proxyPort, headers);
}
@@ -64,14 +56,16 @@ public class WebSocketMain extends Sprite {
return URLUtil.getServerName(this.callerUrl);
}
- public function loadPolicyFile(url:String):void {
- if (policyLoaded && !url) return;
- if (!url) {
- url = "xmlsocket://" + URLUtil.getServerName(this.callerUrl) + ":843";
- }
- log("policy file: " + url);
- Security.loadPolicyFile(url);
- policyLoaded = true;
+ private function loadDefaultPolicyFile(wsUrl:String):void {
+ var policyUrl:String = "xmlsocket://" + URLUtil.getServerName(wsUrl) + ":843";
+ log("policy file: " + policyUrl);
+ Security.loadPolicyFile(policyUrl);
+ }
+
+ public function loadManualPolicyFile(policyUrl:String):void {
+ log("policy file: " + policyUrl);
+ Security.loadPolicyFile(policyUrl);
+ manualPolicyFileLoaded = true;
}
public function log(message:String):void {
View
32 flash-src/WebSocketStateEvent.as
@@ -1,32 +0,0 @@
-// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
-// 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;
- }
-
-}
-
-}
View
142 web_socket.js
@@ -8,8 +8,10 @@
if (window.WebSocket) return;
var console = window.console;
- if (!console) console = {log: function(){ }, error: function(){ }};
-
+ if (!console || !console.log || !console.error) {
+ console = {log: function(){ }, error: function(){ }};
+ }
+
if (!swfobject.hasFlashPlayerVersion("9.0.0")) {
console.error("Flash Player is not installed.");
return;
@@ -31,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";
}
@@ -111,7 +62,7 @@
if (result < 0) { // success
return true;
} else {
- this.bufferedAmount = result;
+ this.bufferedAmount += result;
return false;
}
};
@@ -119,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
@@ -130,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);
}
};
@@ -199,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) {
- e = document.createEvent("MessageEvent");
- e.initMessageEvent("message", false, false, data, null, null, window, null);
- } else { // IE
- 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);
}
- this.onmessage(e);
+ 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);
+ }
+
+ } 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
@@ -237,7 +219,7 @@
}
object.dispatchEvent(event, arguments);
};
- }
+ };
/**
* Basic implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface">DOM 2 EventInterface</a>}
@@ -298,6 +280,12 @@
WebSocket.__tasks = [];
+ WebSocket.loadFlashPolicyFile = function(url) {
+ WebSocket.__addTask(function() {
+ WebSocket.__flash.loadManualPolicyFile(url);
+ });
+ }
+
WebSocket.__initialize = function() {
if (WebSocket.__swfLocation) {
// For backword compatibility.
Please sign in to comment.
Something went wrong with that request. Please try again.