Commit d9205954 authored by Joel Martin's avatar Joel Martin

Import web-socket-js: a0fb3933ce5c824bcb882f5a1cf87e46de773ea8

web-socket-js is a flash based WebSockets emulator.

From: http://github.com/gimite/web-socket-js
parent 5d2c3864
This diff is collapsed.
* How to try
Assuming you have Web server (e.g. Apache) running at http://example.com/ .
- Download web_socket.rb from:
http://github.com/gimite/web-socket-ruby/tree/master
- Run sample Web Socket server (echo server) in example.com with: (#1)
$ ruby web-socket-ruby/samples/echo_server.rb example.com 10081
- If your server already provides socket policy file at port 843, modify the file to allow access to port 10081. Otherwise you can skip this step. See below for details.
- Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html).
- Change ws://localhost:10081 to ws://example.com:10081 in sample.html.
- Open sample.html in your browser.
- After "onopen" is shown, input something, click [Send] and confirm echo back.
#1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com.
* How to debug
If sample.html doesn't work, check these:
- It doesn't work when you open sample.html as local file i.e. file:///.../sample.html. Open it via Web server.
- Make sure port 10081 is not blocked by your server/client's firewall.
- Use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors.
- Install debugger version of Flash Player available here to see Flash errors:
http://www.adobe.com/support/flashplayer/downloads.html
* Supported environment
I confirmed it works on Chrome 3, Firefox 3.5 and IE 8. It may not work in other browsers.
It requires Flash Player 9 or later (probably).
On Chrome 4 Dev Channel, it just uses native Web Socket implementation.
* Flash socket policy file
This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash.
If you use web-socket-ruby available at
http://github.com/gimite/web-socket-ruby/tree/master
, you don't need anything special, because web-socket-ruby handles 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 web-socket-ruby provides.
If you use other Web Socket server implementation, you need to provide socket policy file yourself. See
http://www.lightsphere.com/dev/articles/flash_socket_policy.html
for details and sample script to run socket policy file server.
Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster.
* Cookie considerations
Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard).
Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin.
* Proxy considerations
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 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.
The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails.
* How to build WebSocketMain.swf
Install Flex SDK.
$ cd flash-src
$ mxmlc -output=../WebSocketMain.swf WebSocketMain.as
* License
New BSD License.
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// Lincense: New BSD Lincense
// 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.*;
import com.adobe.net.proxies.RFC2817Socket;
[Event(name="message", type="WebSocketMessageEvent")]
[Event(name="open", type="flash.events.Event")]
[Event(name="close", type="flash.events.Event")]
[Event(name="stateChange", type="WebSocketStateEvent")]
public class WebSocket extends EventDispatcher {
private static var CONNECTING:int = 0;
private static var OPEN:int = 1;
private static var CLOSED:int = 2;
private var socket:RFC2817Socket;
private var main:WebSocketMain;
private var scheme:String;
private var host:String;
private var port:uint;
private var path:String;
private var origin:String;
private var protocol:String;
private var buffer:ByteArray = new ByteArray();
private var headerState:int = 0;
private var readyState:int = CONNECTING;
private var bufferedAmount:int = 0;
private var headers:String;
public function WebSocket(
main:WebSocketMain, url:String, protocol:String,
proxyHost:String = null, proxyPort:int = 0,
headers:String = null) {
this.main = main;
var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?$/);
if (!m) main.fatal("invalid url: " + url);
this.scheme = m[1];
this.host = m[2];
this.port = parseInt(m[4] || "80");
this.path = m[5] || "/";
this.origin = main.getOrigin();
this.protocol = protocol;
// if present and not the empty string, headers MUST end with \r\n
// headers should be zero or more complete lines, for example
// "Header1: xxx\r\nHeader2: yyyy\r\n"
this.headers = headers;
socket = new RFC2817Socket();
// if no proxy information is supplied, it acts like a normal Socket
// @see RFC2817Socket::connect
if (proxyHost != null && proxyPort != 0){
socket.setProxyInfo(proxyHost, proxyPort);
}
socket.addEventListener(Event.CLOSE, onSocketClose);
socket.addEventListener(Event.CONNECT, onSocketConnect);
socket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
socket.connect(host, port);
}
public function send(data:String):int {
if (readyState == OPEN) {
socket.writeByte(0x00);
socket.writeUTFBytes(data);
socket.writeByte(0xff);
socket.flush();
main.log("sent: " + data);
return -1;
} else if (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;
} else {
main.fatal("invalid state");
return 0;
}
}
public function close():void {
main.log("close");
try {
socket.close();
} catch (ex:Error) { }
readyState = CLOSED;
// We don't fire any events here because it causes weird error:
// > You are trying to call recursively into the Flash Player which is not allowed.
// 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");
var hostValue:String = host + (port == 80 ? "" : ":" + port);
var cookie:String = "";
if (main.getCallerHost() == host) {
cookie = ExternalInterface.call("function(){return document.cookie}");
}
var opt:String = "";
if (protocol) opt += "WebSocket-Protocol: " + protocol + "\r\n";
// if caller passes additional headers they must end with "\r\n"
if (headers) opt += headers;
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: {4}\r\n" +
"{3}" +
"\r\n",
path, hostValue, origin, opt, cookie);
main.log("request header:\n" + req);
socket.writeUTFBytes(req);
socket.flush();
}
private function onSocketClose(event:Event):void {
main.log("closed");
readyState = CLOSED;
notifyStateChange();
dispatchEvent(new Event("close"));
}
private function onSocketIoError(event:IOErrorEvent):void {
close();
main.fatal("failed to connect Web Socket server (IoError)");
}
private function onSocketSecurityError(event:SecurityErrorEvent):void {
close();
main.fatal(
"failed to connect Web Socket server (SecurityError)\n" +
"make sure the server is running and Flash socket policy file is correctly placed");
}
private function onSocketData(event:ProgressEvent):void {
var pos:int = buffer.length;
socket.readBytes(buffer, pos);
for (; pos < buffer.length; ++pos) {
if (headerState != 4) {
// try to find "\r\n\r\n"
if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
++headerState;
} else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
++headerState;
} else {
headerState = 0;
}
if (headerState == 4) {
var headerStr:String = buffer.readUTFBytes(pos + 1);
main.log("response header:\n" + headerStr);
validateHeader(headerStr);
makeBufferCompact();
pos = -1;
readyState = OPEN;
notifyStateChange();
dispatchEvent(new Event("open"));
}
} else {
if (buffer[pos] == 0xff) {
if (buffer.readByte() != 0x00) {
close();
main.fatal("data must start with \\x00");
}
var data:String = buffer.readUTFBytes(pos - 1);
main.log("received: " + data);
dispatchEvent(new WebSocketMessageEvent("message", encodeURIComponent(data)));
buffer.readByte();
makeBufferCompact();
pos = -1;
}
}
}
}
private function validateHeader(headerStr:String):void {
var lines:Array = headerStr.split(/\r\n/);
if (!lines[0].match(/^HTTP\/1.1 101 /)) {
close();
main.fatal("bad response: " + lines[0]);
}
var header:Object = {};
for (var i:int = 1; i < lines.length; ++i) {
if (lines[i].length == 0) continue;
var m:Array = lines[i].match(/^(\S+): (.*)$/);
if (!m) {
close();
main.fatal("failed to parse response header line: " + lines[i]);
}
header[m[1]] = m[2];
}
if (header["Upgrade"] != "WebSocket") {
close();
main.fatal("invalid Upgrade: " + header["Upgrade"]);
}
if (header["Connection"] != "Upgrade") {
close();
main.fatal("invalid Connection: " + header["Connection"]);
}
var resOrigin:String = header["WebSocket-Origin"].toLowerCase();
if (resOrigin != origin) {
close();
main.fatal("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
}
if (protocol && header["WebSocket-Protocol"] != protocol) {
close();
main.fatal("protocol doesn't match: '" +
header["WebSocket-Protocol"] + "' != '" + protocol + "'");
}
}
private function makeBufferCompact():void {
if (buffer.position == 0) return;
var nextBuffer:ByteArray = new ByteArray();
buffer.readBytes(nextBuffer);
buffer = nextBuffer;
}
private function notifyStateChange():void {
dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount));
}
}
}
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// Lincense: New BSD Lincense
// 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.*;
import bridge.FABridge;
public class WebSocketMain extends Sprite {
private var policyLoaded:Boolean = false;
private var callerUrl:String;
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 {
callerUrl = url;
}
public function create(
url:String, protocol:String,
proxyHost:String = null, proxyPort:int = 0,
headers:String = null):WebSocket {
loadPolicyFile(null);
return new WebSocket(this, url, protocol, proxyHost, proxyPort, headers);
}
public function getOrigin():String {
return (URLUtil.getProtocol(this.callerUrl) + "://" +
URLUtil.getServerNameWithPort(this.callerUrl)).toLowerCase();
}
public function getCallerHost():String {
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;
}
public function log(message:String):void {
ExternalInterface.call("webSocketLog", encodeURIComponent("[WebSocket] " + message));
}
public function fatal(message:String):void {
ExternalInterface.call("webSocketError", encodeURIComponent("[WebSocket] " + message));
throw message;
}
}
}
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// Lincense: New BSD Lincense
// 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 WebSocketMessageEvent extends Event {
public var data:String;
public function WebSocketMessageEvent(type:String, data:String) {
super(type);
this.data = data;
}
}
}
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// Lincense: New BSD Lincense
// 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;
}
}
}
This diff is collapsed.
/*
Adobe Systems Incorporated(r) Source Code License Agreement
Copyright(c) 2005 Adobe Systems Incorporated. All rights reserved.
Please read this Source Code License Agreement carefully before using
the source code.
Adobe Systems Incorporated grants to you a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable copyright license, to reproduce,
prepare derivative works of, publicly display, publicly perform, and
distribute this source code and such derivative works in source or
object code form without any attribution requirements.
The name "Adobe Systems Incorporated" must not be used to endorse or promote products
derived from the source code without prior written permission.
You agree to indemnify, hold harmless and defend Adobe Systems Incorporated from and
against any loss, damage, claims or lawsuits, including attorney's
fees that arise or result from your use or distribution of the source
code.
THIS SOURCE CODE IS PROVIDED "AS IS" AND "WITH ALL FAULTS", WITHOUT
ANY TECHNICAL SUPPORT OR ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ALSO, THERE IS NO WARRANTY OF
NON-INFRINGEMENT, TITLE OR QUIET ENJOYMENT. IN NO EVENT SHALL MACROMEDIA
OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.adobe.net.proxies
{
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.net.Socket;
/**
* This class allows TCP socket connections through HTTP proxies in accordance with
* RFC 2817:
*
* ftp://ftp.rfc-editor.org/in-notes/rfc2817.txt
*
* It can also be used to make direct connections to a destination, as well. If you
* pass the host and port into the constructor, no proxy will be used. You can also
* call connect, passing in the host and the port, and if you didn't set the proxy
* info, a direct connection will be made. A proxy is only used after you have called
* the setProxyInfo function.
*
* The connection to and negotiation with the proxy is completely hidden. All the
* same events are thrown whether you are using a proxy or not, and the data you
* receive from the target server will look exact as it would if you were connected
* to it directly rather than through a proxy.
*
* @author Christian Cantrell
*
**/
public class RFC2817Socket
extends Socket
{
private var proxyHost:String = null;
private var host:String = null;
private var proxyPort:int = 0;
private var port:int = 0;
private var deferredEventHandlers:Object = new Object();
private var buffer:String = new String();
/**
* Construct a new RFC2817Socket object. If you pass in the host and the port,
* no proxy will be used. If you want to use a proxy, instantiate with no
* arguments, call setProxyInfo, then call connect.
**/
public function RFC2817Socket(host:String = null, port:int = 0)
{
if (host != null && port != 0)
{
super(host, port);
}
}
/**
* Set the proxy host and port number. Your connection will only proxied if
* this function has been called.
**/
public function setProxyInfo(host:String, port:int):void
{
this.proxyHost = host;
this.proxyPort = port;
var deferredSocketDataHandler:Object = this.deferredEventHandlers[ProgressEvent.SOCKET_DATA];
var deferredConnectHandler:Object = this.deferredEventHandlers[Event.CONNECT];
if (deferredSocketDataHandler != null)
{
super.removeEventListener(ProgressEvent.SOCKET_DATA, deferredSocketDataHandler.listener, deferredSocketDataHandler.useCapture);
}
if (deferredConnectHandler != null)
{
super.removeEventListener(Event.CONNECT, deferredConnectHandler.listener, deferredConnectHandler.useCapture);
}
}
/**
* Connect to the specified host over the specified port. If you want your
* connection proxied, call the setProxyInfo function first.
**/
public override function connect(host:String, port:int):void
{
if (this.proxyHost == null)
{
this.redirectConnectEvent();
this.redirectSocketDataEvent();
super.connect(host, port);
}
else
{
this.host = host;
this.port = port;
super.addEventListener(Event.CONNECT, this.onConnect);
super.addEventListener(ProgressEvent.SOCKET_DATA, this.onSocketData);
super.connect(this.proxyHost, this.proxyPort);
}
}
private function onConnect(event:Event):void
{
this.writeUTFBytes("CONNECT "+this.host+":"+this.port+" HTTP/1.1\n\n");
this.flush();
this.redirectConnectEvent();
}
private function onSocketData(event:ProgressEvent):void
{
while (this.bytesAvailable != 0)
{
this.buffer += this.readUTFBytes(1);
if (this.buffer.search(/\r?\n\r?\n$/) != -1)
{
this.checkResponse(event);
break;
}
}
}
private function checkResponse(event:ProgressEvent):void
{
var responseCode:String = this.buffer.substr(this.buffer.indexOf(" ")+1, 3);
if (responseCode.search(/^2/) == -1)
{
var ioError:IOErrorEvent = new IOErrorEvent(IOErrorEvent.IO_ERROR);
ioError.text = "Error connecting to the proxy ["+this.proxyHost+"] on port ["+this.proxyPort+"]: " + this.buffer;
this.dispatchEvent(ioError);
}
else
{
this.redirectSocketDataEvent();
this.dispatchEvent(new Event(Event.CONNECT));
if (this.bytesAvailable > 0)
{
this.dispatchEvent(event);
}
}
this.buffer = null;
}
private function redirectConnectEvent():void
{
super.removeEventListener(Event.CONNECT, onConnect);
var deferredEventHandler:Object = this.deferredEventHandlers[Event.CONNECT];
if (deferredEventHandler != null)
{
super.addEventListener(Event.CONNECT, deferredEventHandler.listener, deferredEventHandler.useCapture, deferredEventHandler.priority, deferredEventHandler.useWeakReference);
}
}
private function redirectSocketDataEvent():void
{
super.removeEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
var deferredEventHandler:Object = this.deferredEventHandlers[ProgressEvent.SOCKET_DATA];
if (deferredEventHandler != null)
{
super.addEventListener(ProgressEvent.SOCKET_DATA, deferredEventHandler.listener, deferredEventHandler.useCapture, deferredEventHandler.priority, deferredEventHandler.useWeakReference);
}
}
public override function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int=0.0, useWeakReference:Boolean=false):void
{
if (type == Event.CONNECT || type == ProgressEvent.SOCKET_DATA)
{
this.deferredEventHandlers[type] = {listener:listener,useCapture:useCapture, priority:priority, useWeakReference:useWeakReference};
}
else
{
super.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
}
}
}
\ No newline at end of file
<!--
Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
Lincense: New BSD Lincense
-->
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Sample of web_socket.js</title>
<!-- Include these three JS files: -->
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript" src="FABridge.js"></script>
<script type="text/javascript" src="web_socket.js"></script>
<script type="text/javascript">
// Set URL of your WebSocketMain.swf here:
WebSocket.__swfLocation = "WebSocketMain.swf";
var ws;
function init() {
// Connect to Web Socket.
// Change host/port here to your own Web Socket server.
ws = new WebSocket("ws://localhost:10081/");
// Set event handlers.
ws.onopen = function() {
output("onopen");
};
ws.onmessage = function(e) {
// e.data contains received string.
output("onmessage: " + e.data);
};
ws.onclose = function() {
output("onclose");
};
}
function onSubmit() {
var input = document.getElementById("input");
// You can send message to the Web Socket using ws.send.
ws.send(input.value);
output("send: " + input.value);
input.value = "";
input.focus();
}
function onCloseClick() {
ws.close();
}
function output(str) {
var log = document.getElementById("log");
var escaped = str.replace(/&/, "&amp;").replace(/</, "&lt;").
replace(/>/, "&gt;").replace(/"/, "&quot;"); // "
log.innerHTML = escaped + "<br>" + log.innerHTML;
}
</script>
</head><body onload="init();">
<form onsubmit="onSubmit(); return false;">
<input type="text" id="input">
<input type="submit" value="Send">
<button onclick="onCloseClick(); return false;">close</button>
</form>
<div id="log"></div>
</body></html>
This diff is collapsed.
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// Lincense: New BSD Lincense
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
(function() {
if (window.WebSocket) return;
var console = window.console;
if (!console) console = {log: function(){ }, error: function(){ }};
function hasFlash() {
if ('navigator' in window && 'plugins' in navigator && navigator.plugins['Shockwave Flash']) {
return !!navigator.plugins['Shockwave Flash'].description;
}
if ('ActiveXObject' in window) {
try {
return !!new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
} catch (e) {}
}
return false;
}
if (!hasFlash()) {
console.error("Flash Player is not installed.");
return;
}
WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
var self = this;
self.readyState = WebSocket.CONNECTING;
self.bufferedAmount = 0;
WebSocket.__addTask(function() {
self.__flash =
WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null);
self.__flash.addEventListener("open", function(fe) {
try {
if (self.onopen) self.onopen();
} catch (e) {
console.error(e.toString());
}
});
self.__flash.addEventListener("close", function(fe) {
try {
if (self.onclose) self.onclose();
} catch (e) {
console.error(e.toString());
}
});
self.__flash.addEventListener("message", function(fe) {
var data = decodeURIComponent(fe.getData());
try {
if (self.onmessage) {
var e;
if (window.MessageEvent) {
e = document.createEvent("MessageEvent");
e.initMessageEvent("message", false, false, data, null, null, window);
} else { // IE
e = {data: data};
}
self.onmessage(e);
}
} catch (e) {
console.error(e.toString());
}
});
self.__flash.addEventListener("stateChange", function(fe) {
try {
self.readyState = fe.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 == WebSocket.CONNECTING) {
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
}
var result = this.__flash.send(data);
if (result < 0) { // success
return true;
} else {
this.bufferedAmount = result;
return false;
}
};
WebSocket.prototype.close = function() {
if (!this.__flash) return;
if (this.readyState != WebSocket.OPEN) return;
this.__flash.close();
// Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events
// which causes weird error:
// > You are trying to call recursively into the Flash Player which is not allowed.
this.readyState = WebSocket.CLOSED;
if (this.onclose) this.onclose();
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {string} type
* @param {function} listener
* @param {boolean} useCapture !NB Not implemented yet
* @return void
*/
WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
if (!('__events' in this)) {
this.__events = {};
}
if (!(type in this.__events)) {
this.__events[type] = [];
if ('function' == typeof this['on' + type]) {
this.__events[type].defaultHandler = this['on' + type];
this['on' + type] = WebSocket_FireEvent(this, type);
}
}
this.__events[type].push(listener);
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {string} type
* @param {function} listener
* @param {boolean} useCapture NB! Not implemented yet
* @return void
*/
WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
if (!('__events' in this)) {
this.__events = {};
}
if (!(type in this.__events)) return;
for (var i = this.__events.length; i > -1; --i) {
if (listener === this.__events[type][i]) {
this.__events[type].splice(i, 1);
break;
}
}
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {WebSocketEvent} event
* @return void
*/
WebSocket.prototype.dispatchEvent = function(event) {
if (!('__events' in this)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
if (!(event.type in this.__events)) throw 'UNSPECIFIED_EVENT_TYPE_ERR';
for (var i = 0, l = this.__events[event.type].length; i < l; ++ i) {
this.__events[event.type][i](event);
if (event.cancelBubble) break;
}
if (false !== event.returnValue &&
'function' == typeof this.__events[event.type].defaultHandler)
{
this.__events[event.type].defaultHandler(event);
}
};
/**
*
* @param {object} object
* @param {string} type
*/
function WebSocket_FireEvent(object, type) {
return function(data) {
var event = new WebSocketEvent();
event.initEvent(type, true, true);
event.target = event.currentTarget = object;
for (var key in data) {
event[key] = data[key];
}
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>}
*
* @class
* @constructor
*/
function WebSocketEvent(){}
/**
*
* @type boolean
*/
WebSocketEvent.prototype.cancelable = true;
/**
*
* @type boolean
*/
WebSocketEvent.prototype.cancelBubble = false;
/**
*
* @return void
*/
WebSocketEvent.prototype.preventDefault = function() {
if (this.cancelable) {
this.returnValue = false;
}
};
/**
*
* @return void
*/
WebSocketEvent.prototype.stopPropagation = function() {
this.cancelBubble = true;
};
/**
*
* @param {string} eventTypeArg
* @param {boolean} canBubbleArg
* @param {boolean} cancelableArg
* @return void
*/
WebSocketEvent.prototype.initEvent = function(eventTypeArg, canBubbleArg, cancelableArg) {
this.type = eventTypeArg;
this.cancelable = cancelableArg;
this.timeStamp = new Date();
};
WebSocket.CONNECTING = 0;
WebSocket.OPEN = 1;
WebSocket.CLOSED = 2;
WebSocket.__tasks = [];
WebSocket.__initialize = function() {
if (!WebSocket.__swfLocation) {
console.error("[WebSocket] set WebSocket.__swfLocation to location of WebSocketMain.swf");
return;
}
var container = document.createElement("div");
container.id = "webSocketContainer";
// Puts the Flash out of the window. Note that we cannot use display: none or visibility: hidden
// here because it prevents Flash from loading at least in IE.
container.style.position = "absolute";
container.style.left = "-100px";
container.style.top = "-100px";
var holder = document.createElement("div");
holder.id = "webSocketFlash";
container.appendChild(holder);
document.body.appendChild(container);
swfobject.embedSWF(
WebSocket.__swfLocation, "webSocketFlash", "8", "8", "9.0.0",
null, {bridgeName: "webSocket"}, null, null,
function(e) {
if (!e.success) console.error("[WebSocket] swfobject.embedSWF failed");
}
);
FABridge.addInitializationCallback("webSocket", function() {
try {
//console.log("[WebSocket] FABridge initializad");
WebSocket.__flash = FABridge.webSocket.root();
WebSocket.__flash.setCallerUrl(location.href);
for (var i = 0; i < WebSocket.__tasks.length; ++i) {
WebSocket.__tasks[i]();
}
WebSocket.__tasks = [];
} catch (e) {
console.error("[WebSocket] " + e.toString());
}
});
};
WebSocket.__addTask = function(task) {
if (WebSocket.__flash) {
task();
} else {
WebSocket.__tasks.push(task);
}
}
// called from Flash
function webSocketLog(message) {
console.log(decodeURIComponent(message));
}
// called from Flash
function webSocketError(message) {
console.error(decodeURIComponent(message));
}
if (window.addEventListener) {
window.addEventListener("load", WebSocket.__initialize, false);
} else {
window.attachEvent("onload", WebSocket.__initialize);
}
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment