summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--NEWS.md17
-rw-r--r--README.md30
-rw-r--r--WebSocketMain.swfbin175928 -> 177139 bytes
-rw-r--r--WebSocketMainInsecure.zipbin166736 -> 170391 bytes
-rw-r--r--flash-src/build.xml2
-rw-r--r--flash-src/src/net/gimite/websocket/WebSocket.as489
-rw-r--r--flash-src/src/net/gimite/websocket/WebSocketEvent.as10
-rw-r--r--flash-src/src/net/gimite/websocket/WebSocketFrame.as21
-rw-r--r--flash-src/src/net/gimite/websocket/WebSocketMain.as14
-rw-r--r--flash-src/src/net/gimite/websocket/WebSocketMainInsecure.as5
-rw-r--r--flash-src/third-party/com/gsolo/encryption/SHA1.as218
-rw-r--r--flash-src/third-party/com/hurlant/crypto/tls/TLSEngine.as1
-rw-r--r--web_socket.js58
14 files changed, 648 insertions, 218 deletions
diff --git a/.gitignore b/.gitignore
index ecf5763..a4513fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
test.html
WebSocket.swc
+flash-src-websocket
diff --git a/NEWS.md b/NEWS.md
new file mode 100644
index 0000000..cec6d97
--- /dev/null
+++ b/NEWS.md
@@ -0,0 +1,17 @@
+- 2011-12-27
+ - web-socket-js now speaks WebSocket defined in RFC 6455, which is
+ equivalent to hybi-13 to hybi-17. It no longer supports old draft
+ protocols.
+
+- 2011-12-17
+ - web-socket-js now uses MozWebSocket when available. i.e. When you load
+ web_socket.js, WebSocket is defined as alias of MozWebSocket when
+ available.
+
+- 2011-09-18
+ - web-socket-js now speaks WebSocket version hybi-10. Old versions spoke
+ hixie-76. If you really need web-socket-js which speaks hixie-76, you can
+ get it from
+ [hixie-76 branch](https://github.com/gimite/web-socket-js/tree/hixie-76),
+ but the branch is no longer maintained. Implementation of hybi-10 is
+ mostly done by Joel Martin (kanaka).
diff --git a/README.md b/README.md
index 7b34ace..b359e4d 100644
--- a/README.md
+++ b/README.md
@@ -54,13 +54,13 @@ $ ruby web-socket-ruby/samples/echo_server.rb example.com 10081
If it doesn't work, try these:
-1. Try Chrome and Firefox 3.x.
+1. Try Chrome and IE 8 or 9.
- It doesn't work on Chrome:<br>
It's likely an issue of your code or the server. Debug your code as usual e.g. using console.log.
- - It works on Chrome but it doesn't work on Firefox:<br>
+ - It works on Chrome but it doesn't work on IE:<br>
It's likely an issue of web-socket-js specific configuration (e.g. 3 and 4 below).
- - It works on both Chrome and Firefox, but it doesn't work on your browser:<br>
+ - It works on both Chrome and IE, but it doesn't work on your browser:<br>
Check "Supported environment" section below. Your browser may not be supported by web-socket-js.
2. Add this line before your code:
@@ -79,13 +79,20 @@ and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.l
8. Install [debugger version of Flash Player](http://www.adobe.com/support/flashplayer/downloads.html) to see Flash errors.
+9. If you followed the steps above and you still have an issue, please [report here](https://github.com/gimite/web-socket-js/issues) with these information:
+
+ - The WebSocket server library you use (e.g. em-websocket, pywebsocket) and its version
+ - The Web browser you use and its version
+ - The exact message you are trying to send from the server or the client
+ - The result of all steps above, especially error message in step 2 if any
+
## Supported environments
It should work on:
-- Google Chrome 4 or later (just uses native implementation)
-- Firefox 3.x, 4.x, Internet Explorer 8, 9 + Flash Player 10 or later
+- Google Chrome 4 or later, Firefox 6 or later (uses native WebSocket or MozWebSocket implementation)
+- Firefox 3 to 5, Internet Explorer 8, 9 + Flash Player 10 or later
It may or may not work on other browsers such as Safari, Opera or IE 6. Patch for these browsers are appreciated, but I will not work on fixing issues specific to these browsers by myself.
@@ -105,7 +112,11 @@ This implementation uses Flash's socket, which means that your server must provi
If you use [web-socket-ruby](http://github.com/gimite/web-socket-ruby/tree/master) or [em-websocket](https://github.com/igrigorik/em-websocket), you don't need anything special, because they handle Flash socket policy file request. But if you already provide socket policy file at port **843**, you need to modify the file to allow access to Web Socket port, because it precedes what the libraries provide.
-If you use other Web Socket server implementation, you need to provide socket policy file yourself. See [Setting up A Flash Socket Policy File](http://www.lightsphere.com/dev/articles/flash_socket_policy.html) for details and sample script to run socket policy file server. [node.js implementation is available here](https://github.com/3rd-Eden/FlashPolicyFileServer).
+If you use other Web Socket server implementation, you need to provide socket policy file yourself. See [Setting up A Flash Socket Policy File](http://www.lightsphere.com/dev/articles/flash_socket_policy.html) for details. Implementation of socket policy file server is available at:
+
+- The article above (Perl implementation)
+- [Ruby implementation](https://github.com/futurechimp/flash_policy_server)
+- [Node.js implementation](https://github.com/3rd-Eden/FlashPolicyFileServer)
Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby or em-websocket. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster.
@@ -121,7 +132,7 @@ Note that it's technically possible that client sends arbitrary string as Cookie
### Proxy support
-[The WebSocket spec](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.
+[The WebSocket spec](http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.
The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy.
@@ -149,9 +160,8 @@ Install [Flex 4 SDK](http://opensource.adobe.com/wiki/display/flexsdk/Download+F
## WebSocket protocol versions
-- web-socket-js supports [Hixie 76 version](http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76) of WebSocket protocol by default i.e. in [master branch](https://github.com/gimite/web-socket-js).
-- If you want to try newer [Hybi 07 version](http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07), check out from [hybi-07 branch](https://github.com/gimite/web-socket-js/tree/hybi-07). This will become the master branch in the future, probably when Chrome switches to Hybi 07.
-- Hixie 75 or before is no longer supported.
+- web-socket-js speaks WebSocket protocol defined in [RFC 6455](http://tools.ietf.org/html/rfc6455).
+- web-socket-js doesn't speak old draft versions of WebSocket protocol including hixie-76, which was supported by old version of this library. If you really need web-socket-js which speaks hixie-76, you can get it from [hixie-76 branch](https://github.com/gimite/web-socket-js/tree/hixie-76), but the branch is no longer maintained.
## License
diff --git a/WebSocketMain.swf b/WebSocketMain.swf
index 657c762..f286c81 100644
--- a/WebSocketMain.swf
+++ b/WebSocketMain.swf
Binary files differ
diff --git a/WebSocketMainInsecure.zip b/WebSocketMainInsecure.zip
index 560a8d4..5a02d72 100644
--- a/WebSocketMainInsecure.zip
+++ b/WebSocketMainInsecure.zip
Binary files differ
diff --git a/flash-src/build.xml b/flash-src/build.xml
index b9f9a70..178f719 100644
--- a/flash-src/build.xml
+++ b/flash-src/build.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Ant build file which provides Yet another way (other than build.sh) to build SWF files.
+ Ant build file which provides yet another way (other than build.sh) to build SWF files.
You need to copy build.properties.sample to build.properties and change FLEX_HOME
for your environment.
diff --git a/flash-src/src/net/gimite/websocket/WebSocket.as b/flash-src/src/net/gimite/websocket/WebSocket.as
index f043b60..dcde61b 100644
--- a/flash-src/src/net/gimite/websocket/WebSocket.as
+++ b/flash-src/src/net/gimite/websocket/WebSocket.as
@@ -1,18 +1,19 @@
// 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-76
+// Reference: http://tools.ietf.org/html/rfc6455
package net.gimite.websocket {
import com.adobe.net.proxies.RFC2817Socket;
-import com.gsolo.encryption.MD5;
+import com.gsolo.encryption.SHA1;
import com.hurlant.crypto.tls.TLSConfig;
import com.hurlant.crypto.tls.TLSEngine;
import com.hurlant.crypto.tls.TLSSecurityParameters;
import com.hurlant.crypto.tls.TLSSocket;
import flash.display.*;
+import flash.errors.*;
import flash.events.*;
import flash.external.*;
import flash.net.*;
@@ -26,16 +27,26 @@ import mx.utils.*;
public class WebSocket extends EventDispatcher {
- private static var CONNECTING:int = 0;
- private static var OPEN:int = 1;
- private static var CLOSING:int = 2;
- private static var CLOSED:int = 3;
+ private static const WEB_SOCKET_GUID:String = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+ private static const CONNECTING:int = 0;
+ private static const OPEN:int = 1;
+ private static const CLOSING:int = 2;
+ private static const CLOSED:int = 3;
+
+ private static const OPCODE_CONTINUATION:int = 0x00;
+ private static const OPCODE_TEXT:int = 0x01;
+ private static const OPCODE_BINARY:int = 0x02;
+ private static const OPCODE_CLOSE:int = 0x08;
+ private static const OPCODE_PING:int = 0x09;
+ private static const OPCODE_PONG:int = 0x0a;
+
+ private static const STATUS_NORMAL_CLOSURE:int = 1000;
+ private static const STATUS_NO_CODE:int = 1005;
+ private static const STATUS_CLOSED_ABNORMALLY:int = 1006;
+ private static const STATUS_CONNECTION_ERROR:int = 5000;
private var id:int;
- private var rawSocket:Socket;
- private var tlsSocket:TLSSocket;
- private var tlsConfig:TLSConfig;
- private var socket:Socket;
private var url:String;
private var scheme:String;
private var host:String;
@@ -43,16 +54,24 @@ public class WebSocket extends EventDispatcher {
private var path:String;
private var origin:String;
private var requestedProtocols:Array;
+ private var cookie:String;
+ private var headers:String;
+
+ private var rawSocket:Socket;
+ private var tlsSocket:TLSSocket;
+ private var tlsConfig:TLSConfig;
+ private var socket:Socket;
+
private var acceptedProtocol:String;
+ private var expectedDigest:String;
+
private var buffer:ByteArray = new ByteArray();
private var headerState:int = 0;
private var readyState:int = CONNECTING;
- private var cookie:String;
- private var headers:String;
- private var noiseChars:Array;
- private var expectedDigest:String;
+
private var logger:IWebSocketLogger;
-
+ private var base64Encoder:Base64Encoder = new Base64Encoder();
+
public function WebSocket(
id:int, url:String, protocols:Array, origin:String,
proxyHost:String, proxyPort:int,
@@ -60,7 +79,6 @@ public class WebSocket extends EventDispatcher {
logger:IWebSocketLogger) {
this.logger = logger;
this.id = id;
- initNoiseChars();
this.url = url;
var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?(\?.*)?$/);
if (!m) fatal("SYNTAX_ERR: invalid url: " + url);
@@ -127,36 +145,75 @@ public class WebSocket extends EventDispatcher {
}
public function send(encData:String):int {
- var data:String = decodeURIComponent(encData);
+ var data:String;
+ try {
+ data = decodeURIComponent(encData);
+ } catch (ex:URIError) {
+ logger.error("SYNTAX_ERR: URIError in send()");
+ return 0;
+ }
+ logger.log("send: " + data);
+ var dataBytes:ByteArray = new ByteArray();
+ dataBytes.writeUTFBytes(data);
if (readyState == OPEN) {
- socket.writeByte(0x00);
- socket.writeUTFBytes(data);
- socket.writeByte(0xff);
- socket.flush();
- logger.log("sent: " + data);
- return -1;
+ var frame:WebSocketFrame = new WebSocketFrame();
+ frame.opcode = OPCODE_TEXT;
+ frame.payload = dataBytes;
+ if (sendFrame(frame)) {
+ return -1;
+ } else {
+ return dataBytes.length;
+ }
} else if (readyState == CLOSING || readyState == CLOSED) {
- var bytes:ByteArray = new ByteArray();
- bytes.writeUTFBytes(data);
- return bytes.length; // not sure whether it should include \x00 and \xff
+ return dataBytes.length;
} else {
fatal("invalid state");
return 0;
}
}
- public function close(isError:Boolean = false):void {
- logger.log("close");
+ public function close(
+ code:int = STATUS_NO_CODE, reason:String = "", origin:String = "client"):void {
+ if (code != STATUS_NORMAL_CLOSURE &&
+ code != STATUS_NO_CODE &&
+ code != STATUS_CONNECTION_ERROR) {
+ logger.error(StringUtil.substitute(
+ "Fail connection by {0}: code={1} reason={2}", origin, code, reason));
+ }
+ var closeConnection:Boolean =
+ code == STATUS_CONNECTION_ERROR || origin == "server";
try {
- if (readyState == OPEN && !isError) {
- socket.writeByte(0xff);
- socket.writeByte(0x00);
- socket.flush();
+ if (readyState == OPEN && code != STATUS_CONNECTION_ERROR) {
+ var frame:WebSocketFrame = new WebSocketFrame();
+ frame.opcode = OPCODE_CLOSE;
+ frame.payload = new ByteArray();
+ if (origin == "client" && code != STATUS_NO_CODE) {
+ frame.payload.writeShort(code);
+ frame.payload.writeUTFBytes(reason);
+ }
+ sendFrame(frame);
}
- socket.close();
- } catch (ex:Error) { }
- readyState = CLOSED;
- this.dispatchEvent(new WebSocketEvent(isError ? "error" : "close"));
+ if (closeConnection) {
+ socket.close();
+ }
+ } catch (ex:Error) {
+ logger.error("Error: " + ex.message);
+ }
+ if (closeConnection) {
+ logger.log("closed");
+ var fireErrorEvent:Boolean = readyState != CONNECTING && code == STATUS_CONNECTION_ERROR;
+ readyState = CLOSED;
+ if (fireErrorEvent) {
+ dispatchEvent(new WebSocketEvent("error"));
+ } else {
+ var wasClean:Boolean = code != STATUS_CLOSED_ABNORMALLY && code != STATUS_CONNECTION_ERROR;
+ var eventCode:int = code == STATUS_CONNECTION_ERROR ? STATUS_CLOSED_ABNORMALLY : code;
+ dispatchCloseEvent(wasClean, eventCode, reason);
+ }
+ } else {
+ logger.log("closing");
+ readyState = CLOSING;
+ }
}
private function onSocketConnect(event:Event):void {
@@ -169,10 +226,11 @@ public class WebSocket extends EventDispatcher {
var defaultPort:int = scheme == "wss" ? 443 : 80;
var hostValue:String = host + (port == defaultPort ? "" : ":" + port);
- var key1:String = generateKey();
- var key2:String = generateKey();
- var key3:String = generateKey3();
- expectedDigest = getSecurityDigest(key1, key2, key3);
+ var key:String = generateKey();
+
+ SHA1.b64pad = "=";
+ expectedDigest = SHA1.b64_sha1(key + WEB_SOCKET_GUID);
+
var opt:String = "";
if (requestedProtocols.length > 0) {
opt += "Sec-WebSocket-Protocol: " + requestedProtocols.join(",") + "\r\n";
@@ -182,27 +240,25 @@ public class WebSocket extends EventDispatcher {
var req:String = StringUtil.substitute(
"GET {0} HTTP/1.1\r\n" +
- "Upgrade: WebSocket\r\n" +
- "Connection: Upgrade\r\n" +
"Host: {1}\r\n" +
- "Origin: {2}\r\n" +
- "Cookie: {3}\r\n" +
- "Sec-WebSocket-Key1: {4}\r\n" +
- "Sec-WebSocket-Key2: {5}\r\n" +
- "{6}" +
+ "Upgrade: websocket\r\n" +
+ "Connection: Upgrade\r\n" +
+ "Sec-WebSocket-Key: {2}\r\n" +
+ "Origin: {3}\r\n" +
+ "Sec-WebSocket-Version: 13\r\n" +
+ "Cookie: {4}\r\n" +
+ "{5}" +
"\r\n",
- path, hostValue, origin, cookie, key1, key2, opt);
+ path, hostValue, key, origin, cookie, opt);
logger.log("request header:\n" + req);
socket.writeUTFBytes(req);
- logger.log("sent key3: " + key3);
- writeBytes(key3);
socket.flush();
}
private function onSocketClose(event:Event):void {
logger.log("closed");
readyState = CLOSED;
- this.dispatchEvent(new WebSocketEvent("close"));
+ dispatchCloseEvent(false, STATUS_CLOSED_ABNORMALLY, "");
}
private function onSocketIoError(event:IOErrorEvent):void {
@@ -214,7 +270,7 @@ public class WebSocket extends EventDispatcher {
"error communicating with Web Socket server at " + url +
" (IoError: " + event.text + ")";
}
- onError(message);
+ onConnectionError(message);
}
private function onSocketSecurityError(event:SecurityErrorEvent):void {
@@ -228,13 +284,13 @@ public class WebSocket extends EventDispatcher {
"error communicating with Web Socket server at " + url +
" (SecurityError: " + event.text + ")";
}
- onError(message);
+ onConnectionError(message);
}
- private function onError(message:String):void {
+ private function onConnectionError(message:String):void {
if (readyState == CLOSED) return;
logger.error(message);
- close(readyState != CONNECTING);
+ close(STATUS_CONNECTION_ERROR);
}
private function onSocketData(event:ProgressEvent):void {
@@ -253,92 +309,114 @@ public class WebSocket extends EventDispatcher {
if (headerState == 4) {
var headerStr:String = readUTFBytes(buffer, 0, pos + 1);
logger.log("response header:\n" + headerStr);
- if (!validateHeader(headerStr)) return;
- removeBufferBefore(pos + 1);
- pos = -1;
- }
- } else if (headerState == 4) {
- if (pos == 15) {
- var replyDigest:String = readBytes(buffer, 0, 16);
- logger.log("reply digest: " + replyDigest);
- if (replyDigest != expectedDigest) {
- onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
- return;
- }
- headerState = 5;
+ if (!validateHandshake(headerStr)) return;
removeBufferBefore(pos + 1);
pos = -1;
readyState = OPEN;
this.dispatchEvent(new WebSocketEvent("open"));
}
} else {
- if (buffer[pos] == 0xff && pos > 0) {
- if (buffer[0] != 0x00) {
- onError("data must start with \\x00");
- return;
- }
- var data:String = readUTFBytes(buffer, 1, pos - 1);
- logger.log("received: " + data);
- this.dispatchEvent(new WebSocketEvent("message", encodeURIComponent(data)));
- removeBufferBefore(pos + 1);
- pos = -1;
- } else if (pos == 1 && buffer[0] == 0xff && buffer[1] == 0x00) { // closing
- logger.log("received closing packet");
- removeBufferBefore(pos + 1);
+ var frame:WebSocketFrame = parseFrame();
+ if (frame) {
+ removeBufferBefore(frame.length);
pos = -1;
- close();
+ if (frame.rsv != 0) {
+ close(1002, "RSV must be 0.");
+ } else if (frame.mask) {
+ close(1002, "Frame from server must not be masked.");
+ } else if (frame.opcode >= 0x08 && frame.opcode <= 0x0f && frame.payload.length >= 126) {
+ close(1004, "Payload of control frame must be less than 126 bytes.");
+ } else {
+ switch (frame.opcode) {
+ case OPCODE_CONTINUATION:
+ close(1003, "Received continuation frame, which is not implemented.");
+ break;
+ case OPCODE_TEXT:
+ var data:String = readUTFBytes(frame.payload, 0, frame.payload.length);
+ try {
+ this.dispatchEvent(new WebSocketEvent("message", encodeURIComponent(data)));
+ } catch (ex:URIError) {
+ close(1007, "URIError while encoding the received data.");
+ }
+ break;
+ case OPCODE_BINARY:
+ // See https://github.com/gimite/web-socket-js/pull/89
+ // for discussion about supporting binary data.
+ close(1003, "Received binary data, which is not supported.");
+ break;
+ case OPCODE_CLOSE:
+ // Extracts code and reason string.
+ var code:int = STATUS_NO_CODE;
+ var reason:String = "";
+ if (frame.payload.length >= 2) {
+ frame.payload.endian = Endian.BIG_ENDIAN;
+ frame.payload.position = 0;
+ code = frame.payload.readUnsignedShort();
+ reason = readUTFBytes(frame.payload, 2, frame.payload.length - 2);
+ }
+ logger.log("received closing frame");
+ close(code, reason, "server");
+ break;
+ case OPCODE_PING:
+ sendPong(frame.payload);
+ break;
+ case OPCODE_PONG:
+ break;
+ default:
+ close(1002, "Received unknown opcode: " + frame.opcode);
+ break;
+ }
+ }
}
}
}
}
- private function validateHeader(headerStr:String):Boolean {
+ private function validateHandshake(headerStr:String):Boolean {
var lines:Array = headerStr.split(/\r\n/);
if (!lines[0].match(/^HTTP\/1.1 101 /)) {
- onError("bad response: " + lines[0]);
+ onConnectionError("bad response: " + lines[0]);
return false;
}
var header:Object = {};
var lowerHeader:Object = {};
for (var i:int = 1; i < lines.length; ++i) {
if (lines[i].length == 0) continue;
- var m:Array = lines[i].match(/^(\S+): (.*)$/);
+ var m:Array = lines[i].match(/^(\S+):(.*)$/);
if (!m) {
- onError("failed to parse response header line: " + lines[i]);
+ onConnectionError("failed to parse response header line: " + lines[i]);
return false;
}
- header[m[1].toLowerCase()] = m[2];
- lowerHeader[m[1].toLowerCase()] = m[2].toLowerCase();
+ var key:String = m[1].toLowerCase();
+ var value:String = StringUtil.trim(m[2]);
+ header[key] = value;
+ lowerHeader[key] = value.toLowerCase();
}
if (lowerHeader["upgrade"] != "websocket") {
- onError("invalid Upgrade: " + header["Upgrade"]);
+ onConnectionError("invalid Upgrade: " + header["Upgrade"]);
return false;
}
if (lowerHeader["connection"] != "upgrade") {
- onError("invalid Connection: " + header["Connection"]);
+ onConnectionError("invalid Connection: " + header["Connection"]);
return false;
}
- if (!lowerHeader["sec-websocket-origin"]) {
- if (lowerHeader["websocket-origin"]) {
- onError(
- "The WebSocket server speaks old WebSocket protocol, " +
- "which is not supported by web-socket-js. " +
- "It requires WebSocket protocol 76 or later. " +
- "Try newer version of the server if available.");
- } else {
- onError("header Sec-WebSocket-Origin is missing");
- }
+ if (!lowerHeader["sec-websocket-accept"]) {
+ onConnectionError(
+ "The WebSocket server speaks old WebSocket protocol, " +
+ "which is not supported by web-socket-js. " +
+ "It requires WebSocket protocol HyBi 10. " +
+ "Try newer version of the server if available.");
return false;
}
- var resOrigin:String = lowerHeader["sec-websocket-origin"];
- if (resOrigin != origin) {
- onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
+ var replyDigest:String = header["sec-websocket-accept"]
+ if (replyDigest != expectedDigest) {
+ onConnectionError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
return false;
}
if (requestedProtocols.length > 0) {
acceptedProtocol = header["sec-websocket-protocol"];
if (requestedProtocols.indexOf(acceptedProtocol) < 0) {
- onError("protocol doesn't match: '" +
+ onConnectionError("protocol doesn't match: '" +
acceptedProtocol + "' not in '" + requestedProtocols.join(",") + "'");
return false;
}
@@ -346,89 +424,147 @@ public class WebSocket extends EventDispatcher {
return true;
}
- private function removeBufferBefore(pos:int):void {
- if (pos == 0) return;
- var nextBuffer:ByteArray = new ByteArray();
- buffer.position = pos;
- buffer.readBytes(nextBuffer);
- buffer = nextBuffer;
+ private function sendPong(payload:ByteArray):Boolean {
+ var frame:WebSocketFrame = new WebSocketFrame();
+ frame.opcode = OPCODE_PONG;
+ frame.payload = payload;
+ return sendFrame(frame);
}
- private function initNoiseChars():void {
- noiseChars = new Array();
- for (var i:int = 0x21; i <= 0x2f; ++i) {
- noiseChars.push(String.fromCharCode(i));
+ private function sendFrame(frame:WebSocketFrame):Boolean {
+
+ var plength:uint = frame.payload.length;
+
+ // Generates a mask.
+ var mask:ByteArray = new ByteArray();
+ for (var i:int = 0; i < 4; i++) {
+ mask.writeByte(randomInt(0, 255));
}
- for (var j:int = 0x3a; j <= 0x7a; ++j) {
- noiseChars.push(String.fromCharCode(j));
+
+ var header:ByteArray = new ByteArray();
+ // FIN + RSV + opcode
+ header.writeByte((frame.fin ? 0x80 : 0x00) | (frame.rsv << 4) | frame.opcode);
+ if (plength <= 125) {
+ header.writeByte(0x80 | plength); // Masked + length
+ } else if (plength > 125 && plength < 65536) {
+ header.writeByte(0x80 | 126); // Masked + 126
+ header.writeShort(plength);
+ } else if (plength >= 65536 && plength < 4294967296) {
+ header.writeByte(0x80 | 127); // Masked + 127
+ header.writeUnsignedInt(0); // zero high order bits
+ header.writeUnsignedInt(plength);
+ } else {
+ fatal("Send frame size too large");
}
- }
-
- private function generateKey():String {
- var spaces:uint = randomInt(1, 12);
- var max:uint = uint.MAX_VALUE / spaces;
- var number:uint = randomInt(0, max);
- var key:String = (number * spaces).toString();
- var noises:int = randomInt(1, 12);
- var pos:int;
- for (var i:int = 0; i < noises; ++i) {
- var char:String = noiseChars[randomInt(0, noiseChars.length - 1)];
- pos = randomInt(0, key.length);
- key = key.substr(0, pos) + char + key.substr(pos);
+ header.writeBytes(mask);
+
+ var maskedPayload:ByteArray = new ByteArray();
+ maskedPayload.length = frame.payload.length;
+ for (i = 0; i < frame.payload.length; i++) {
+ maskedPayload[i] = mask[i % 4] ^ frame.payload[i];
}
- for (var j:int = 0; j < spaces; ++j) {
- pos = randomInt(1, key.length - 1);
- key = key.substr(0, pos) + " " + key.substr(pos);
+
+ try {
+ socket.writeBytes(header);
+ socket.writeBytes(maskedPayload);
+ socket.flush();
+ } catch (ex:Error) {
+ logger.error("Error while sending frame: " + ex.message);
+ setTimeout(function():void {
+ if (readyState != CLOSED) {
+ close(STATUS_CONNECTION_ERROR);
+ }
+ }, 0);
+ return false;
}
- return key;
+ return true;
+
}
-
- private function generateKey3():String {
- var key3:String = "";
- for (var i:int = 0; i < 8; ++i) {
- key3 += String.fromCharCode(randomInt(0, 255));
+
+ private function parseFrame():WebSocketFrame {
+
+ var frame:WebSocketFrame = new WebSocketFrame();
+ var hlength:uint = 0;
+ var plength:uint = 0;
+
+ hlength = 2;
+ if (buffer.length < hlength) {
+ return null;
}
- return key3;
- }
-
- private function getSecurityDigest(key1:String, key2:String, key3:String):String {
- var bytes1:String = keyToBytes(key1);
- var bytes2:String = keyToBytes(key2);
- return MD5.rstr_md5(bytes1 + bytes2 + key3);
- }
-
- private function keyToBytes(key:String):String {
- var keyNum:uint = parseInt(key.replace(/[^\d]/g, ""));
- var spaces:uint = 0;
- for (var i:int = 0; i < key.length; ++i) {
- if (key.charAt(i) == " ") ++spaces;
+
+ frame.fin = (buffer[0] & 0x80) != 0;
+ frame.rsv = (buffer[0] & 0x70) >> 4;
+ frame.opcode = buffer[0] & 0x0f;
+ // Payload unmasking is not implemented because masking frames from server
+ // is not allowed. This field is used only for error checking.
+ frame.mask = (buffer[1] & 0x80) != 0;
+ plength = buffer[1] & 0x7f;
+
+ if (plength == 126) {
+
+ hlength = 4;
+ if (buffer.length < hlength) {
+ return null;
+ }
+ buffer.endian = Endian.BIG_ENDIAN;
+ buffer.position = 2;
+ plength = buffer.readUnsignedShort();
+
+ } else if (plength == 127) {
+
+ hlength = 10;
+ if (buffer.length < hlength) {
+ return null;
+ }
+ buffer.endian = Endian.BIG_ENDIAN;
+ buffer.position = 2;
+ // Protocol allows 64-bit length, but we only handle 32-bit
+ var big:uint = buffer.readUnsignedInt(); // Skip high 32-bits
+ plength = buffer.readUnsignedInt(); // Low 32-bits
+ if (big != 0) {
+ fatal("Frame length exceeds 4294967295. Bailing out!");
+ return null;
+ }
+
}
- var resultNum:uint = keyNum / spaces;
- var bytes:String = "";
- for (var j:int = 3; j >= 0; --j) {
- bytes += String.fromCharCode((resultNum >> (j * 8)) & 0xff);
+
+ if (buffer.length < hlength + plength) {
+ return null;
}
- return bytes;
+
+ frame.length = hlength + plength;
+ frame.payload = new ByteArray();
+ buffer.position = hlength;
+ buffer.readBytes(frame.payload, 0, plength);
+ return frame;
+
}
- // Writes byte sequence to socket.
- // bytes is String in special format where bytes[i] is i-th byte, not i-th character.
- private function writeBytes(bytes:String):void {
- for (var i:int = 0; i < bytes.length; ++i) {
- socket.writeByte(bytes.charCodeAt(i));
- }
+ private function dispatchCloseEvent(wasClean:Boolean, code:int, reason:String):void {
+ var event:WebSocketEvent = new WebSocketEvent("close");
+ event.wasClean = wasClean;
+ event.code = code;
+ event.reason = reason;
+ dispatchEvent(event);
}
- // 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, 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.
- bytes += String.fromCharCode(buffer.readByte() & 0xff);
+ private function removeBufferBefore(pos:int):void {
+ if (pos == 0) return;
+ var nextBuffer:ByteArray = new ByteArray();
+ buffer.position = pos;
+ buffer.readBytes(nextBuffer);
+ buffer = nextBuffer;
+ }
+
+ private function generateKey():String {
+ var vals:ByteArray = new ByteArray();
+ vals.length = 16;
+ for (var i:int = 0; i < vals.length; ++i) {
+ vals[i] = randomInt(0, 127);
}
- return bytes;
+ base64Encoder.reset();
+ base64Encoder.encodeBytes(vals);
+ return base64Encoder.toString();
}
private function readUTFBytes(buffer:ByteArray, start:int, numBytes:int):String {
@@ -454,15 +590,6 @@ public class WebSocket extends EventDispatcher {
throw message;
}
- // for debug
- private function dumpBytes(bytes:String):void {
- var output:String = "";
- for (var i:int = 0; i < bytes.length; ++i) {
- output += bytes.charCodeAt(i).toString() + ", ";
- }
- logger.log(output);
- }
-
}
}
diff --git a/flash-src/src/net/gimite/websocket/WebSocketEvent.as b/flash-src/src/net/gimite/websocket/WebSocketEvent.as
index aa17cac..1188f58 100644
--- a/flash-src/src/net/gimite/websocket/WebSocketEvent.as
+++ b/flash-src/src/net/gimite/websocket/WebSocketEvent.as
@@ -14,6 +14,9 @@ public class WebSocketEvent extends Event {
public static const ERROR:String = "error";
public var message:String;
+ public var wasClean:Boolean;
+ public var code:int;
+ public var reason:String;
public function WebSocketEvent(
type:String, message:String = null, bubbles:Boolean = false, cancelable:Boolean = false) {
@@ -22,7 +25,12 @@ public class WebSocketEvent extends Event {
}
public override function clone():Event {
- return new WebSocketEvent(this.type, this.message, this.bubbles, this.cancelable);
+ var event:WebSocketEvent = new WebSocketEvent(
+ this.type, this.message, this.bubbles, this.cancelable);
+ event.wasClean = wasClean;
+ event.code = code;
+ event.reason = reason;
+ return event;
}
public override function toString():String {
diff --git a/flash-src/src/net/gimite/websocket/WebSocketFrame.as b/flash-src/src/net/gimite/websocket/WebSocketFrame.as
new file mode 100644
index 0000000..1ce1c54
--- /dev/null
+++ b/flash-src/src/net/gimite/websocket/WebSocketFrame.as
@@ -0,0 +1,21 @@
+// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
+// License: New BSD License
+
+package net.gimite.websocket {
+
+import flash.utils.ByteArray;
+
+public class WebSocketFrame {
+
+ public var fin:Boolean = true;
+ public var rsv:int = 0;
+ public var opcode:int = -1;
+ public var payload:ByteArray;
+
+ // Fields below are not used when used as a parameter of sendFrame().
+ public var length:uint = 0;
+ public var mask:Boolean = false;
+
+}
+
+}
diff --git a/flash-src/src/net/gimite/websocket/WebSocketMain.as b/flash-src/src/net/gimite/websocket/WebSocketMain.as
index 28cd66d..3daa572 100644
--- a/flash-src/src/net/gimite/websocket/WebSocketMain.as
+++ b/flash-src/src/net/gimite/websocket/WebSocketMain.as
@@ -1,7 +1,5 @@
// 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-76
package net.gimite.websocket {
@@ -40,6 +38,9 @@ public class WebSocketMain extends Sprite implements IWebSocketLogger{
public function setDebug(val:Boolean):void {
debug = val;
+ if (val) {
+ log("debug enabled");
+ }
}
private function loadDefaultPolicyFile(wsUrl:String):void {
@@ -74,6 +75,15 @@ public class WebSocketMain extends Sprite implements IWebSocketLogger{
if (event.message !== null) {
eventObj.message = event.message;
}
+ if (event.wasClean) {
+ eventObj.wasClean = event.wasClean;
+ }
+ if (event.code) {
+ eventObj.code = event.code;
+ }
+ if (event.reason !== null) {
+ eventObj.reason = event.reason;
+ }
return eventObj;
}
diff --git a/flash-src/src/net/gimite/websocket/WebSocketMainInsecure.as b/flash-src/src/net/gimite/websocket/WebSocketMainInsecure.as
index e845839..7a368a0 100644
--- a/flash-src/src/net/gimite/websocket/WebSocketMainInsecure.as
+++ b/flash-src/src/net/gimite/websocket/WebSocketMainInsecure.as
@@ -1,7 +1,5 @@
// 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-76
package net.gimite.websocket {
@@ -11,6 +9,9 @@ public class WebSocketMainInsecure extends WebSocketMain {
public function WebSocketMainInsecure() {
Security.allowDomain("*");
+ // Also allows HTTP -> HTTPS call. Since we have already allowed arbitrary domains, allowing
+ // HTTP -> HTTPS would not be more dangerous.
+ Security.allowInsecureDomain("*");
super();
}
diff --git a/flash-src/third-party/com/gsolo/encryption/SHA1.as b/flash-src/third-party/com/gsolo/encryption/SHA1.as
new file mode 100644
index 0000000..12f64c5
--- /dev/null
+++ b/flash-src/third-party/com/gsolo/encryption/SHA1.as
@@ -0,0 +1,218 @@
+package com.gsolo.encryption {
+ public class SHA1 {
+ /*
+ * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
+ * in FIPS PUB 180-1
+ * Version 2.1a Copyright Paul Johnston 2000 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for details.
+ *
+ * Converted to AS3 By Geoffrey Williams
+ */
+
+ /*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+
+ public static const HEX_FORMAT_LOWERCASE:uint = 0;
+ public static const HEX_FORMAT_UPPERCASE:uint = 1;
+
+ public static const BASE64_PAD_CHARACTER_DEFAULT_COMPLIANCE:String = "";
+ public static const BASE64_PAD_CHARACTER_RFC_COMPLIANCE:String = "=";
+
+ public static const BITS_PER_CHAR_ASCII:uint = 8;
+ public static const BITS_PER_CHAR_UNICODE:uint = 8;
+
+ public static var hexcase:uint = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
+ public static var b64pad:String = ""; /* base-64 pad character. "=" for strict RFC compliance */
+ public static var chrsz:uint = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
+
+ public static function encrypt (string:String):String {
+ return hex_sha1 (string);
+ }
+
+ /*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+ public static function hex_sha1 (string:String):String {
+ return binb2hex (core_sha1( str2binb(string), string.length * chrsz));
+ }
+
+ public static function b64_sha1 (string:String):String {
+ return binb2b64 (core_sha1 (str2binb (string), string.length * chrsz));
+ }
+
+ public static function str_sha1 (string:String):String {
+ return binb2str (core_sha1 (str2binb (string), string.length * chrsz));
+ }
+
+ public static function hex_hmac_sha1 (key:String, data:String):String {
+ return binb2hex (core_hmac_sha1 (key, data));
+ }
+
+ public static function b64_hmac_sha1 (key:String, data:String):String {
+ return binb2b64 (core_hmac_sha1 (key, data));
+ }
+
+ public static function str_hmac_sha1 (key:String, data:String):String {
+ return binb2str (core_hmac_sha1 (key, data));
+ }
+
+ /*
+ * Perform a simple self-test to see if the VM is working
+ */
+ public static function sha1_vm_test ():Boolean {
+ return hex_sha1 ("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
+ }
+
+ /*
+ * Calculate the SHA-1 of an array of big-endian words, and a bit length
+ */
+ public static function core_sha1 (x:Array, len:Number):Array {
+ /* append padding */
+ x[len >> 5] |= 0x80 << (24 - len % 32);
+ x[((len + 64 >> 9) << 4) + 15] = len;
+
+ var w:Array = new Array(80);
+ var a:Number = 1732584193;
+ var b:Number = -271733879;
+ var c:Number = -1732584194;
+ var d:Number = 271733878;
+ var e:Number = -1009589776;
+
+ for(var i:Number = 0; i < x.length; i += 16) {
+ var olda:Number = a;
+ var oldb:Number = b;
+ var oldc:Number = c;
+ var oldd:Number = d;
+ var olde:Number = e;
+
+ for(var j:Number = 0; j < 80; j++) {
+ if(j < 16) w[j] = x[i + j];
+ else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
+ var t:Number = safe_add (safe_add (rol (a, 5), sha1_ft (j, b, c, d)), safe_add (safe_add (e, w[j]), sha1_kt (j)));
+ e = d;
+ d = c;
+ c = rol(b, 30);
+ b = a;
+ a = t;
+ }
+
+ a = safe_add(a, olda);
+ b = safe_add(b, oldb);
+ c = safe_add(c, oldc);
+ d = safe_add(d, oldd);
+ e = safe_add(e, olde);
+ }
+
+ return [a, b, c, d, e];
+ }
+
+ /*
+ * Perform the appropriate triplet combination function for the current
+ * iteration
+ */
+ public static function sha1_ft (t:Number, b:Number, c:Number, d:Number):Number {
+ if(t < 20) return (b & c) | ((~b) & d);
+ if(t < 40) return b ^ c ^ d;
+ if(t < 60) return (b & c) | (b & d) | (c & d);
+ return b ^ c ^ d;
+ }
+
+ /*
+ * Determine the appropriate additive constant for the current iteration
+ */
+ public static function sha1_kt (t:Number):Number {
+ return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : (t < 60) ? -1894007588 : -899497514;
+ }
+
+ /*
+ * Calculate the HMAC-SHA1 of a key and some data
+ */
+ public static function core_hmac_sha1 (key:String, data:String):Array {
+ var bkey:Array = str2binb (key);
+ if (bkey.length > 16) bkey = core_sha1 (bkey, key.length * chrsz);
+
+ var ipad:Array = new Array(16), opad:Array = new Array(16);
+ for(var i:Number = 0; i < 16; i++) {
+ ipad[i] = bkey[i] ^ 0x36363636;
+ opad[i] = bkey[i] ^ 0x5C5C5C5C;
+ }
+
+ var hash:Array = core_sha1 (ipad.concat (str2binb(data)), 512 + data.length * chrsz);
+ return core_sha1 (opad.concat (hash), 512 + 160);
+ }
+
+ /*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+ public static function safe_add (x:Number, y:Number):Number {
+ var lsw:Number = (x & 0xFFFF) + (y & 0xFFFF);
+ var msw:Number = (x >> 16) + (y >> 16) + (lsw >> 16);
+ return (msw << 16) | (lsw & 0xFFFF);
+ }
+
+ /*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+ public static function rol (num:Number, cnt:Number):Number {
+ return (num << cnt) | (num >>> (32 - cnt));
+ }
+
+ /*
+ * Convert an 8-bit or 16-bit string to an array of big-endian words
+ * In 8-bit function, characters >255 have their hi-byte silently ignored.
+ */
+ public static function str2binb (str:String):Array {
+ var bin:Array = new Array ();
+ var mask:Number = (1 << chrsz) - 1;
+ for (var i:Number = 0; i < str.length * chrsz; i += chrsz) bin[i>>5] |= (str.charCodeAt (i / chrsz) & mask) << (32 - chrsz - i%32);
+ return bin;
+ }
+
+ /*
+ * Convert an array of big-endian words to a string
+ */
+ public static function binb2str (bin:Array):String {
+ var str:String = "";
+ var mask:Number = (1 << chrsz) - 1;
+ for (var i:Number = 0; i < bin.length * 32; i += chrsz) str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
+ return str;
+ }
+
+ /*
+ * Convert an array of big-endian words to a hex string.
+ */
+ public static function binb2hex (binarray:Array):String {
+ var hex_tab:String = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+ var str:String = "";
+ for(var i:Number = 0; i < binarray.length * 4; i++) {
+ str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
+ hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
+ }
+ return str;
+ }
+
+ /*
+ * Convert an array of big-endian words to a base-64 string
+ */
+ public static function binb2b64 (binarray:Array):String {
+ var tab:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var str:String = "";
+ for(var i:Number = 0; i < binarray.length * 4; i += 3) {
+ var triplet:Number = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
+ | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
+ | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
+ for(var j:Number = 0; j < 4; j++) {
+ if (i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+ else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+ }
+ }
+ return str;
+ }
+ }
+}
diff --git a/flash-src/third-party/com/hurlant/crypto/tls/TLSEngine.as b/flash-src/third-party/com/hurlant/crypto/tls/TLSEngine.as
index 72f3941..1ae7cad 100644
--- a/flash-src/third-party/com/hurlant/crypto/tls/TLSEngine.as
+++ b/flash-src/third-party/com/hurlant/crypto/tls/TLSEngine.as
@@ -715,6 +715,7 @@ package com.hurlant.crypto.tls {
rec.writeBytes(data, offset, 16384);
rec.position = 0;
sendRecord(PROTOCOL_APPLICATION_DATA, rec);
+ rec.length = 0;
offset += 16384;
len -= 16384;
}
diff --git a/web_socket.js b/web_socket.js
index 947a637..06cc5d0 100644
--- a/web_socket.js
+++ b/web_socket.js
@@ -1,11 +1,19 @@
// 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
+// Reference: http://tools.ietf.org/html/rfc6455
(function() {
- if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) return;
+ if (window.WEB_SOCKET_FORCE_FLASH) {
+ // Keeps going.
+ } else if (window.WebSocket) {
+ return;
+ } else if (window.MozWebSocket) {
+ // Firefox.
+ window.WebSocket = MozWebSocket;
+ return;
+ }
var logger;
if (window.WEB_SOCKET_LOGGER) {
@@ -30,14 +38,14 @@
}
/**
- * This class represents a faux web socket.
+ * Our own implementation of WebSocket class using Flash.
* @param {string} url
* @param {array or string} protocols
* @param {string} proxyHost
* @param {int} proxyPort
* @param {string} headers
*/
- WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
+ window.WebSocket = function(url, protocols, proxyHost, proxyPort, headers) {
var self = this;
self.__id = WebSocket.__nextId++;
WebSocket.__instances[self.__id] = self;
@@ -91,10 +99,10 @@
*/
WebSocket.prototype.close = function() {
if (this.__createTask) {
- clearTimeout(this.__createTask);
- this.__createTask = null;
- this.readyState = WebSocket.CLOSED;
- return;
+ clearTimeout(this.__createTask);
+ this.__createTask = null;
+ this.readyState = WebSocket.CLOSED;
+ return;
}
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
return;
@@ -149,7 +157,7 @@
events[i](event);
}
var handler = this["on" + event.type];
- if (handler) handler(event);
+ if (handler) handler.apply(this, [event]);
};
/**
@@ -157,6 +165,7 @@
* @param {Object} flashEvent
*/
WebSocket.prototype.__handleEvent = function(flashEvent) {
+
if ("readyState" in flashEvent) {
this.readyState = flashEvent.readyState;
}
@@ -168,8 +177,10 @@
if (flashEvent.type == "open" || flashEvent.type == "error") {
jsEvent = this.__createSimpleEvent(flashEvent.type);
} else if (flashEvent.type == "close") {
- // TODO implement jsEvent.wasClean
jsEvent = this.__createSimpleEvent("close");
+ jsEvent.wasClean = flashEvent.wasClean ? true : false;
+ jsEvent.code = flashEvent.code;
+ jsEvent.reason = flashEvent.reason;
} else if (flashEvent.type == "message") {
var data = decodeURIComponent(flashEvent.message);
jsEvent = this.__createMessageEvent("message", data);
@@ -178,6 +189,7 @@
}
this.dispatchEvent(jsEvent);
+
};
WebSocket.prototype.__createSimpleEvent = function(type) {
@@ -209,6 +221,9 @@
WebSocket.CLOSING = 2;
WebSocket.CLOSED = 3;
+ // Field to check implementation of WebSocket.
+ WebSocket.__isFlashImplementation = true;
+ WebSocket.__initialized = false;
WebSocket.__flash = null;
WebSocket.__instances = {};
WebSocket.__tasks = [];
@@ -228,7 +243,9 @@
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
*/
WebSocket.__initialize = function() {
- if (WebSocket.__flash) return;
+
+ if (WebSocket.__initialized) return;
+ WebSocket.__initialized = true;
if (WebSocket.__swfLocation) {
// For backword compatibility.
@@ -286,7 +303,9 @@
if (!e.success) {
logger.error("[WebSocket] swfobject.embedSWF failed");
}
- });
+ }
+ );
+
};
/**
@@ -361,15 +380,12 @@
};
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
- if (window.addEventListener) {
- window.addEventListener("load", function(){
- WebSocket.__initialize();
- }, false);
- } else {
- window.attachEvent("onload", function(){
- WebSocket.__initialize();
- });
- }
+ // NOTE:
+ // This fires immediately if web_socket.js is dynamically loaded after
+ // the document is loaded.
+ swfobject.addDomLoadEvent(function() {
+ WebSocket.__initialize();
+ });
}
})();