summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWtritch <wtritchler@semanticresearch.com>2011-02-22 15:45:26 -0800
committerWtritch <wtritchler@semanticresearch.com>2011-02-22 15:45:26 -0800
commiteeedd74380ff3f3a9f9a71777e361537058701f1 (patch)
tree1adbc1dd5b8dc6816d093458c344204081158c44
parentccc3dcfb38919c876864a6dc1aa40a6566991c51 (diff)
downloadweb-socket-js-eeedd74380ff3f3a9f9a71777e361537058701f1.tar.gz
Completed updates: Removed FABridge dependence. Created javascript WebSocketController to manage multiple instances. Created central ExternalInterface manager at flash-src/bridge/JSBridge.as
-rw-r--r--FABridge.js604
-rwxr-xr-x[-rw-r--r--]WebSocketMain.swfbin180028 -> 252352 bytes
-rw-r--r--WebSocketMainInsecure.zipbin172823 -> 245413 bytes
-rw-r--r--flash-src/WebSocket.as67
-rw-r--r--flash-src/WebSocketMain.as225
-rw-r--r--flash-src/bridge/FABridge.as943
-rwxr-xr-x[-rw-r--r--]sample.html1
-rwxr-xr-x[-rw-r--r--]web_socket.js827
8 files changed, 657 insertions, 2010 deletions
diff --git a/FABridge.js b/FABridge.js
deleted file mode 100644
index df7e355..0000000
--- a/FABridge.js
+++ /dev/null
@@ -1,604 +0,0 @@
-/*
-/*
-Copyright 2006 Adobe Systems Incorporated
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
-to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
-OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-*/
-
-
-/*
- * The Bridge class, responsible for navigating AS instances
- */
-function FABridge(target,bridgeName)
-{
- this.target = target;
- this.remoteTypeCache = {};
- this.remoteInstanceCache = {};
- this.remoteFunctionCache = {};
- this.localFunctionCache = {};
- this.bridgeID = FABridge.nextBridgeID++;
- this.name = bridgeName;
- this.nextLocalFuncID = 0;
- FABridge.instances[this.name] = this;
- FABridge.idMap[this.bridgeID] = this;
-
- return this;
-}
-
-// type codes for packed values
-FABridge.TYPE_ASINSTANCE = 1;
-FABridge.TYPE_ASFUNCTION = 2;
-
-FABridge.TYPE_JSFUNCTION = 3;
-FABridge.TYPE_ANONYMOUS = 4;
-
-FABridge.initCallbacks = {};
-FABridge.userTypes = {};
-
-FABridge.addToUserTypes = function()
-{
- for (var i = 0; i < arguments.length; i++)
- {
- FABridge.userTypes[arguments[i]] = {
- 'typeName': arguments[i],
- 'enriched': false
- };
- }
-}
-
-FABridge.argsToArray = function(args)
-{
- var result = [];
- for (var i = 0; i < args.length; i++)
- {
- result[i] = args[i];
- }
- return result;
-}
-
-function instanceFactory(objID)
-{
- this.fb_instance_id = objID;
- return this;
-}
-
-function FABridge__invokeJSFunction(args)
-{
- var funcID = args[0];
- var throughArgs = args.concat();//FABridge.argsToArray(arguments);
- throughArgs.shift();
-
- var bridge = FABridge.extractBridgeFromID(funcID);
- return bridge.invokeLocalFunction(funcID, throughArgs);
-}
-
-FABridge.addInitializationCallback = function(bridgeName, callback)
-{
- var inst = FABridge.instances[bridgeName];
- if (inst != undefined)
- {
- callback.call(inst);
- return;
- }
-
- var callbackList = FABridge.initCallbacks[bridgeName];
- if(callbackList == null)
- {
- FABridge.initCallbacks[bridgeName] = callbackList = [];
- }
-
- callbackList.push(callback);
-}
-
-// updated for changes to SWFObject2
-function FABridge__bridgeInitialized(bridgeName) {
- var objects = document.getElementsByTagName("object");
- var ol = objects.length;
- var activeObjects = [];
- if (ol > 0) {
- for (var i = 0; i < ol; i++) {
- if (typeof objects[i].SetVariable != "undefined") {
- activeObjects[activeObjects.length] = objects[i];
- }
- }
- }
- var embeds = document.getElementsByTagName("embed");
- var el = embeds.length;
- var activeEmbeds = [];
- if (el > 0) {
- for (var j = 0; j < el; j++) {
- if (typeof embeds[j].SetVariable != "undefined") {
- activeEmbeds[activeEmbeds.length] = embeds[j];
- }
- }
- }
- var aol = activeObjects.length;
- var ael = activeEmbeds.length;
- var searchStr = "bridgeName="+ bridgeName;
- if ((aol == 1 && !ael) || (aol == 1 && ael == 1)) {
- FABridge.attachBridge(activeObjects[0], bridgeName);
- }
- else if (ael == 1 && !aol) {
- FABridge.attachBridge(activeEmbeds[0], bridgeName);
- }
- else {
- var flash_found = false;
- if (aol > 1) {
- for (var k = 0; k < aol; k++) {
- var params = activeObjects[k].childNodes;
- for (var l = 0; l < params.length; l++) {
- var param = params[l];
- if (param.nodeType == 1 && param.tagName.toLowerCase() == "param" && param["name"].toLowerCase() == "flashvars" && param["value"].indexOf(searchStr) >= 0) {
- FABridge.attachBridge(activeObjects[k], bridgeName);
- flash_found = true;
- break;
- }
- }
- if (flash_found) {
- break;
- }
- }
- }
- if (!flash_found && ael > 1) {
- for (var m = 0; m < ael; m++) {
- var flashVars = activeEmbeds[m].attributes.getNamedItem("flashVars").nodeValue;
- if (flashVars.indexOf(searchStr) >= 0) {
- FABridge.attachBridge(activeEmbeds[m], bridgeName);
- break;
- }
- }
- }
- }
- return true;
-}
-
-// used to track multiple bridge instances, since callbacks from AS are global across the page.
-
-FABridge.nextBridgeID = 0;
-FABridge.instances = {};
-FABridge.idMap = {};
-FABridge.refCount = 0;
-
-FABridge.extractBridgeFromID = function(id)
-{
- var bridgeID = (id >> 16);
- return FABridge.idMap[bridgeID];
-}
-
-FABridge.attachBridge = function(instance, bridgeName)
-{
- var newBridgeInstance = new FABridge(instance, bridgeName);
-
- FABridge[bridgeName] = newBridgeInstance;
-
-/* FABridge[bridgeName] = function() {
- return newBridgeInstance.root();
- }
-*/
- var callbacks = FABridge.initCallbacks[bridgeName];
- if (callbacks == null)
- {
- return;
- }
- for (var i = 0; i < callbacks.length; i++)
- {
- callbacks[i].call(newBridgeInstance);
- }
- delete FABridge.initCallbacks[bridgeName]
-}
-
-// some methods can't be proxied. You can use the explicit get,set, and call methods if necessary.
-
-FABridge.blockedMethods =
-{
- toString: true,
- get: true,
- set: true,
- call: true
-};
-
-FABridge.prototype =
-{
-
-
-// bootstrapping
-
- root: function()
- {
- return this.deserialize(this.target.getRoot());
- },
-//clears all of the AS objects in the cache maps
- releaseASObjects: function()
- {
- return this.target.releaseASObjects();
- },
-//clears a specific object in AS from the type maps
- releaseNamedASObject: function(value)
- {
- if(typeof(value) != "object")
- {
- return false;
- }
- else
- {
- var ret = this.target.releaseNamedASObject(value.fb_instance_id);
- return ret;
- }
- },
-//create a new AS Object
- create: function(className)
- {
- return this.deserialize(this.target.create(className));
- },
-
-
- // utilities
-
- makeID: function(token)
- {
- return (this.bridgeID << 16) + token;
- },
-
-
- // low level access to the flash object
-
-//get a named property from an AS object
- getPropertyFromAS: function(objRef, propName)
- {
- if (FABridge.refCount > 0)
- {
- throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
- }
- else
- {
- FABridge.refCount++;
- retVal = this.target.getPropFromAS(objRef, propName);
- retVal = this.handleError(retVal);
- FABridge.refCount--;
- return retVal;
- }
- },
-//set a named property on an AS object
- setPropertyInAS: function(objRef,propName, value)
- {
- if (FABridge.refCount > 0)
- {
- throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
- }
- else
- {
- FABridge.refCount++;
- retVal = this.target.setPropInAS(objRef,propName, this.serialize(value));
- retVal = this.handleError(retVal);
- FABridge.refCount--;
- return retVal;
- }
- },
-
-//call an AS function
- callASFunction: function(funcID, args)
- {
- if (FABridge.refCount > 0)
- {
- throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
- }
- else
- {
- FABridge.refCount++;
- retVal = this.target.invokeASFunction(funcID, this.serialize(args));
- retVal = this.handleError(retVal);
- FABridge.refCount--;
- return retVal;
- }
- },
-//call a method on an AS object
- callASMethod: function(objID, funcName, args)
- {
- if (FABridge.refCount > 0)
- {
- throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround.");
- }
- else
- {
- FABridge.refCount++;
- args = this.serialize(args);
- retVal = this.target.invokeASMethod(objID, funcName, args);
- retVal = this.handleError(retVal);
- FABridge.refCount--;
- return retVal;
- }
- },
-
- // responders to remote calls from flash
-
- //callback from flash that executes a local JS function
- //used mostly when setting js functions as callbacks on events
- invokeLocalFunction: function(funcID, args)
- {
- var result;
- var func = this.localFunctionCache[funcID];
-
- if(func != undefined)
- {
- result = this.serialize(func.apply(null, this.deserialize(args)));
- }
-
- return result;
- },
-
- // Object Types and Proxies
-
- // accepts an object reference, returns a type object matching the obj reference.
- getTypeFromName: function(objTypeName)
- {
- return this.remoteTypeCache[objTypeName];
- },
- //create an AS proxy for the given object ID and type
- createProxy: function(objID, typeName)
- {
- var objType = this.getTypeFromName(typeName);
- instanceFactory.prototype = objType;
- var instance = new instanceFactory(objID);
- this.remoteInstanceCache[objID] = instance;
- return instance;
- },
- //return the proxy associated with the given object ID
- getProxy: function(objID)
- {
- return this.remoteInstanceCache[objID];
- },
-
- // accepts a type structure, returns a constructed type
- addTypeDataToCache: function(typeData)
- {
- var newType = new ASProxy(this, typeData.name);
- var accessors = typeData.accessors;
- for (var i = 0; i < accessors.length; i++)
- {
- this.addPropertyToType(newType, accessors[i]);
- }
-
- var methods = typeData.methods;
- for (var i = 0; i < methods.length; i++)
- {
- if (FABridge.blockedMethods[methods[i]] == undefined)
- {
- this.addMethodToType(newType, methods[i]);
- }
- }
-
-
- this.remoteTypeCache[newType.typeName] = newType;
- return newType;
- },
-
- //add a property to a typename; used to define the properties that can be called on an AS proxied object
- addPropertyToType: function(ty, propName)
- {
- var c = propName.charAt(0);
- var setterName;
- var getterName;
- if(c >= "a" && c <= "z")
- {
- getterName = "get" + c.toUpperCase() + propName.substr(1);
- setterName = "set" + c.toUpperCase() + propName.substr(1);
- }
- else
- {
- getterName = "get" + propName;
- setterName = "set" + propName;
- }
- ty[setterName] = function(val)
- {
- this.bridge.setPropertyInAS(this.fb_instance_id, propName, val);
- }
- ty[getterName] = function()
- {
- return this.bridge.deserialize(this.bridge.getPropertyFromAS(this.fb_instance_id, propName));
- }
- },
-
- //add a method to a typename; used to define the methods that can be callefd on an AS proxied object
- addMethodToType: function(ty, methodName)
- {
- ty[methodName] = function()
- {
- return this.bridge.deserialize(this.bridge.callASMethod(this.fb_instance_id, methodName, FABridge.argsToArray(arguments)));
- }
- },
-
- // Function Proxies
-
- //returns the AS proxy for the specified function ID
- getFunctionProxy: function(funcID)
- {
- var bridge = this;
- if (this.remoteFunctionCache[funcID] == null)
- {
- this.remoteFunctionCache[funcID] = function()
- {
- bridge.callASFunction(funcID, FABridge.argsToArray(arguments));
- }
- }
- return this.remoteFunctionCache[funcID];
- },
-
- //reutrns the ID of the given function; if it doesnt exist it is created and added to the local cache
- getFunctionID: function(func)
- {
- if (func.__bridge_id__ == undefined)
- {
- func.__bridge_id__ = this.makeID(this.nextLocalFuncID++);
- this.localFunctionCache[func.__bridge_id__] = func;
- }
- return func.__bridge_id__;
- },
-
- // serialization / deserialization
-
- serialize: function(value)
- {
- var result = {};
-
- var t = typeof(value);
- //primitives are kept as such
- if (t == "number" || t == "string" || t == "boolean" || t == null || t == undefined)
- {
- result = value;
- }
- else if (value instanceof Array)
- {
- //arrays are serializesd recursively
- result = [];
- for (var i = 0; i < value.length; i++)
- {
- result[i] = this.serialize(value[i]);
- }
- }
- else if (t == "function")
- {
- //js functions are assigned an ID and stored in the local cache
- result.type = FABridge.TYPE_JSFUNCTION;
- result.value = this.getFunctionID(value);
- }
- else if (value instanceof ASProxy)
- {
- result.type = FABridge.TYPE_ASINSTANCE;
- result.value = value.fb_instance_id;
- }
- else
- {
- result.type = FABridge.TYPE_ANONYMOUS;
- result.value = value;
- }
-
- return result;
- },
-
- //on deserialization we always check the return for the specific error code that is used to marshall NPE's into JS errors
- // the unpacking is done by returning the value on each pachet for objects/arrays
- deserialize: function(packedValue)
- {
-
- var result;
-
- var t = typeof(packedValue);
- if (t == "number" || t == "string" || t == "boolean" || packedValue == null || packedValue == undefined)
- {
- result = this.handleError(packedValue);
- }
- else if (packedValue instanceof Array)
- {
- result = [];
- for (var i = 0; i < packedValue.length; i++)
- {
- result[i] = this.deserialize(packedValue[i]);
- }
- }
- else if (t == "object")
- {
- for(var i = 0; i < packedValue.newTypes.length; i++)
- {
- this.addTypeDataToCache(packedValue.newTypes[i]);
- }
- for (var aRefID in packedValue.newRefs)
- {
- this.createProxy(aRefID, packedValue.newRefs[aRefID]);
- }
- if (packedValue.type == FABridge.TYPE_PRIMITIVE)
- {
- result = packedValue.value;
- }
- else if (packedValue.type == FABridge.TYPE_ASFUNCTION)
- {
- result = this.getFunctionProxy(packedValue.value);
- }
- else if (packedValue.type == FABridge.TYPE_ASINSTANCE)
- {
- result = this.getProxy(packedValue.value);
- }
- else if (packedValue.type == FABridge.TYPE_ANONYMOUS)
- {
- result = packedValue.value;
- }
- }
- return result;
- },
- //increases the reference count for the given object
- addRef: function(obj)
- {
- this.target.incRef(obj.fb_instance_id);
- },
- //decrease the reference count for the given object and release it if needed
- release:function(obj)
- {
- this.target.releaseRef(obj.fb_instance_id);
- },
-
- // check the given value for the components of the hard-coded error code : __FLASHERROR
- // used to marshall NPE's into flash
-
- handleError: function(value)
- {
- if (typeof(value)=="string" && value.indexOf("__FLASHERROR")==0)
- {
- var myErrorMessage = value.split("||");
- if(FABridge.refCount > 0 )
- {
- FABridge.refCount--;
- }
- throw new Error(myErrorMessage[1]);
- return value;
- }
- else
- {
- return value;
- }
- }
-};
-
-// The root ASProxy class that facades a flash object
-
-ASProxy = function(bridge, typeName)
-{
- this.bridge = bridge;
- this.typeName = typeName;
- return this;
-};
-//methods available on each ASProxy object
-ASProxy.prototype =
-{
- get: function(propName)
- {
- return this.bridge.deserialize(this.bridge.getPropertyFromAS(this.fb_instance_id, propName));
- },
-
- set: function(propName, value)
- {
- this.bridge.setPropertyInAS(this.fb_instance_id, propName, value);
- },
-
- call: function(funcName, args)
- {
- this.bridge.callASMethod(this.fb_instance_id, funcName, args);
- },
-
- addRef: function() {
- this.bridge.addRef(this);
- },
-
- release: function() {
- this.bridge.release(this);
- }
-};
diff --git a/WebSocketMain.swf b/WebSocketMain.swf
index 694f9dc..123b170 100644..100755
--- a/WebSocketMain.swf
+++ b/WebSocketMain.swf
Binary files differ
diff --git a/WebSocketMainInsecure.zip b/WebSocketMainInsecure.zip
index ad3c9b9..99b6f66 100644
--- a/WebSocketMainInsecure.zip
+++ b/WebSocketMainInsecure.zip
Binary files differ
diff --git a/flash-src/WebSocket.as b/flash-src/WebSocket.as
index 0733570..e9faedd 100644
--- a/flash-src/WebSocket.as
+++ b/flash-src/WebSocket.as
@@ -5,22 +5,24 @@
package {
+import com.adobe.net.proxies.RFC2817Socket;
+import com.gsolo.encryption.MD5;
+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.events.*;
import flash.external.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
-import mx.core.*;
+
import mx.controls.*;
+import mx.core.*;
import mx.events.*;
import mx.utils.*;
-import com.adobe.net.proxies.RFC2817Socket;
-import com.hurlant.crypto.tls.TLSSocket;
-import com.hurlant.crypto.tls.TLSConfig;
-import com.hurlant.crypto.tls.TLSEngine;
-import com.hurlant.crypto.tls.TLSSecurityParameters;
-import com.gsolo.encryption.MD5;
[Event(name="event", type="flash.events.Event")]
public class WebSocket extends EventDispatcher {
@@ -30,6 +32,8 @@ public class WebSocket extends EventDispatcher {
private static var CLOSING:int = 2;
private static var CLOSED:int = 3;
+ private var socketId : Number;
+
private var rawSocket:Socket;
private var tlsSocket:TLSSocket;
private var tlsConfig:TLSConfig;
@@ -43,7 +47,6 @@ public class WebSocket extends EventDispatcher {
private var origin:String;
private var protocol:String;
private var buffer:ByteArray = new ByteArray();
- private var eventQueue:Array = [];
private var headerState:int = 0;
private var readyState:int = CONNECTING;
private var headers:String;
@@ -51,10 +54,11 @@ public class WebSocket extends EventDispatcher {
private var expectedDigest:String;
public function WebSocket(
- main:WebSocketMain, url:String, protocol:String,
+ main:WebSocketMain, webSocketId : Number, url:String, protocol:String,
proxyHost:String = null, proxyPort:int = 0,
headers:String = null) {
this.main = main;
+ this.socketId = webSocketId;
initNoiseChars();
this.url = url;
var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?$/);
@@ -101,6 +105,22 @@ public class WebSocket extends EventDispatcher {
rawSocket.connect(host, port);
}
+ /**
+ * socketId accessor
+ * @return This WebSocket's ID.
+ */
+ public function get webSocketId() : Number {
+ return this.socketId;
+ }
+
+ /**
+ * readyState accessor
+ * @return this WebSocket's readyState.
+ */
+ public function get readOnly_readyState() : int {
+ return this.readyState;
+ }
+
public function send(encData:String):int {
var data:String = decodeURIComponent(encData);
if (readyState == OPEN) {
@@ -122,7 +142,6 @@ public class WebSocket extends EventDispatcher {
public function close():void {
main.log("close");
- eventQueue = [];
try {
if (readyState == OPEN) {
socket.writeByte(0xff);
@@ -132,9 +151,7 @@ public class WebSocket extends EventDispatcher {
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.
+ this.dispatchEvent(new WebSocketEvent(WebSocketEvent.CLOSE));
}
private function onSocketConnect(event:Event):void {
@@ -181,7 +198,7 @@ public class WebSocket extends EventDispatcher {
private function onSocketClose(event:Event):void {
main.log("closed");
readyState = CLOSED;
- fireEvent({type: "close"}, true);
+ this.dispatchEvent(new WebSocketEvent(WebSocketEvent.CLOSE));
}
private function onSocketIoError(event:IOErrorEvent):void {
@@ -210,8 +227,9 @@ public class WebSocket extends EventDispatcher {
var state:int = readyState;
if (state == CLOSED) return;
main.error(message);
+
+ this.dispatchEvent(new WebSocketEvent(WebSocketEvent.ERROR, encodeURIComponent(message)));
close();
- fireEvent({type: state == CONNECTING ? "close" : "error"}, true);
}
private function onSocketData(event:ProgressEvent):void {
@@ -246,7 +264,7 @@ public class WebSocket extends EventDispatcher {
removeBufferBefore(pos + 1);
pos = -1;
readyState = OPEN;
- fireEvent({type: "open"}, true);
+ this.dispatchEvent(new WebSocketEvent(WebSocketEvent.OPEN));
}
} else {
if (buffer[pos] == 0xff && pos > 0) {
@@ -256,7 +274,7 @@ public class WebSocket extends EventDispatcher {
}
var data:String = readUTFBytes(buffer, 1, pos - 1);
main.log("received: " + data);
- fireEvent({type: "message", data: encodeURIComponent(data)}, false);
+ this.dispatchEvent(new WebSocketEvent(WebSocketEvent.MESSAGE, encodeURIComponent(data)));
removeBufferBefore(pos + 1);
pos = -1;
} else if (pos == 1 && buffer[0] == 0xff && buffer[1] == 0x00) { // closing
@@ -264,17 +282,10 @@ public class WebSocket extends EventDispatcher {
removeBufferBefore(pos + 1);
pos = -1;
close();
- fireEvent({type: "close"}, true);
}
}
}
}
-
- public function receiveEvents():Array {
- var q:Array = eventQueue;
- eventQueue = [];
- return q;
- }
private function validateHeader(headerStr:String):Boolean {
var lines:Array = headerStr.split(/\r\n/);
@@ -335,14 +346,6 @@ public class WebSocket extends EventDispatcher {
buffer = nextBuffer;
}
- private function fireEvent(event:Object, stateChanged:Boolean):void {
- if (stateChanged) {
- event.readyState = readyState;
- }
- eventQueue.push(event);
- dispatchEvent(new Event("event"));
- }
-
private function initNoiseChars():void {
noiseChars = new Array();
for (var i:int = 0x21; i <= 0x2f; ++i) {
diff --git a/flash-src/WebSocketMain.as b/flash-src/WebSocketMain.as
index 9d6ff90..987b30a 100644
--- a/flash-src/WebSocketMain.as
+++ b/flash-src/WebSocketMain.as
@@ -4,85 +4,148 @@
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
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 callerUrl:String;
- private var debug:Boolean = false;
- private var manualPolicyFileLoaded:Boolean = false;
-
- public function WebSocketMain() {
- var fab:FABridge = new FABridge();
- fab.rootObject = this;
- //log("Flash initialized");
- }
-
- public function setCallerUrl(url:String):void {
- callerUrl = url;
- }
-
- public function setDebug(val:Boolean):void {
- debug = val;
- }
-
- public function create(
- url:String, protocol:String,
- proxyHost:String = null, proxyPort:int = 0,
- headers:String = null):WebSocket {
- if (!manualPolicyFileLoaded) {
- loadDefaultPolicyFile(url);
- }
- 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);
- }
-
- 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 {
- if (debug) {
- ExternalInterface.call("webSocketLog", encodeURIComponent("[WebSocket] " + message));
- }
- }
-
- public function error(message:String):void {
- ExternalInterface.call("webSocketError", encodeURIComponent("[WebSocket] " + message));
- }
-
- public function fatal(message:String):void {
- ExternalInterface.call("webSocketError", encodeURIComponent("[WebSocket] " + message));
- throw message;
- }
-
-}
-
+
+ import bridge.JSBridge;
+
+ import flash.display.Sprite;
+ import flash.system.Security;
+ import flash.utils.setTimeout;
+
+ import mx.utils.URLUtil;
+
+ public class WebSocketMain extends Sprite {
+
+ private var callerUrl:String;
+ private var debug:Boolean = false;
+ private var manualPolicyFileLoaded:Boolean = false;
+ private var jsBridge : JSBridge;
+ private var webSockets : Array;
+ private var eventQueue : Array;
+
+ public function WebSocketMain() {
+ this.jsBridge = new JSBridge(this);
+ webSockets = new Array();
+ eventQueue = new Array();
+ jsBridge.flashInitialized();
+ }
+
+ /*************
+ * Initialization / Utility methods
+ */
+
+ public function setCallerUrl(url:String):void {
+ callerUrl = url;
+ }
+
+ public function setDebug(val:Boolean):void {
+ debug = val;
+ }
+
+ public function getOrigin():String {
+ return (URLUtil.getProtocol(this.callerUrl) + "://" +
+ URLUtil.getServerNameWithPort(this.callerUrl)).toLowerCase();
+ }
+
+ public function getCallerHost():String {
+ return URLUtil.getServerName(this.callerUrl);
+ }
+
+ 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 {
+ if (debug) {
+ jsBridge.log(message);
+ }
+ }
+
+ public function error(message:String):void {
+ jsBridge.error(message);
+ }
+
+ public function fatal(message:String):void {
+ jsBridge.error(message);
+ }
+
+ private function parseEvent(event : WebSocketEvent) : Object {
+ var eventObj : Object = new Object();
+ var webSocket : WebSocket = event.target as WebSocket;
+ eventObj.type = event.type;
+ eventObj.webSocketId = webSocket.webSocketId;
+ eventObj.readyState = webSocket.readOnly_readyState;
+ if (event.message !== null) {
+ eventObj.message = event.message;
+ }
+ return eventObj;
+ }
+
+ /**
+ * Socket interface
+ */
+ public function create(
+ webSocketId : Number,
+ url:String, protocol:String,
+ proxyHost:String = null, proxyPort:int = 0,
+ headers:String = null):void {
+ if (!manualPolicyFileLoaded) {
+ loadDefaultPolicyFile(url);
+ }
+ var newSocket : WebSocket = new WebSocket(this, webSocketId, url, protocol, proxyHost, proxyPort, headers);
+ newSocket.addEventListener(WebSocketEvent.OPEN, onSocketEvent);
+ newSocket.addEventListener(WebSocketEvent.CLOSE, onSocketEvent);
+ newSocket.addEventListener(WebSocketEvent.ERROR, onSocketEvent);
+ newSocket.addEventListener(WebSocketEvent.MESSAGE, onSocketEvent);
+ webSockets[webSocketId] = newSocket;
+ }
+
+ public function send(webSocketId : Number, encData : String) : int {
+ var webSocket : WebSocket = webSockets[webSocketId];
+ return webSocket.send(encData);
+ }
+
+ public function close(webSocketId : Number) : void {
+ var webSocket : WebSocket = webSockets[webSocketId];
+ webSocket.close();
+ }
+
+
+ /****************
+ * Socket event handler
+ */
+ public function onSocketEvent(event : WebSocketEvent) : void {
+ var eventObj : Object = parseEvent(event);
+ eventQueue.push(eventObj);
+ setTimeout(processEvents, 1);
+ }
+
+ /**
+ * Process our event queue. If javascript is unresponsive, set
+ * a timeout and try again.
+ */
+ public function processEvents() : void {
+ var eventObj : Object;
+ var success : Boolean;
+ while (eventQueue.length > 0) {
+ eventObj = eventQueue[0];
+ success = jsBridge.sendEvent(eventObj);
+ if (!success) {
+ setTimeout(processEvents, 500);
+ break;
+ } else {
+ eventQueue.shift();
+ }
+ }
+ }
+
+ }
+
}
diff --git a/flash-src/bridge/FABridge.as b/flash-src/bridge/FABridge.as
deleted file mode 100644
index d03dba0..0000000
--- a/flash-src/bridge/FABridge.as
+++ /dev/null
@@ -1,943 +0,0 @@
-/*
-Copyright � 2006 Adobe Systems Incorporated
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
-to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
-OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-*/
-
-
-/*
- * The Bridge class, responsible for navigating JS instances
- */
-package bridge
-{
-
-/*
- * imports
- */
-import flash.external.ExternalInterface;
-import flash.utils.Timer;
-import flash.events.*;
-import flash.display.DisplayObject;
-import flash.system.ApplicationDomain;
-import flash.utils.Dictionary;
-import flash.utils.setTimeout;
-
-import mx.collections.errors.ItemPendingError;
-import mx.core.IMXMLObject;
-
-import flash.utils.getQualifiedClassName;
-import flash.utils.describeType;
-import flash.events.TimerEvent;
-
-/**
- * The FABridge class, responsible for proxying AS objects into javascript
- */
-public class FABridge extends EventDispatcher implements IMXMLObject
-{
-
- //holds a list of stuff to call later, to break the recurrence of the js <> as calls
- //you must use the full class name, as returned by the getQualifiedClassName() function
- public static const MethodsToCallLater:Object = new Object();
- MethodsToCallLater["mx.collections::ArrayCollection"]="refresh,removeItemAt";
-
- public static const EventsToCallLater:Object = new Object();
- EventsToCallLater["mx.data.events::UnresolvedConflictsEvent"]="true";
- EventsToCallLater["mx.events::PropertyChangeEvent"]="true";
-
- public static const INITIALIZED:String = "bridgeInitialized";
-
- // constructor
- public function FABridge()
- {
- super();
- initializeCallbacks();
- }
-
- // private vars
-
- /**
- * stores a cache of descriptions of AS types suitable for sending to JS
- */
- private var localTypeMap:Dictionary = new Dictionary();
-
- /**
- * stores an id-referenced dictionary of objects exported to JS
- */
- private var localInstanceMap:Dictionary = new Dictionary();
-
- /**
- * stores an id-referenced dictionary of functions exported to JS
- */
- private var localFunctionMap:Dictionary = new Dictionary();
-
- /**
- * stores an id-referenced dictionary of proxy functions imported from JS
- */
- private var remoteFunctionCache:Dictionary = new Dictionary();
-
- /**
- * stores a list of custom serialization functions
- */
- private var customSerializersMap:Dictionary = new Dictionary();
-
- /**
- * stores a map of object ID's and their reference count
- */
- private var refMap:Dictionary = new Dictionary();
- /**
- * a local counter for generating unique IDs
- */
- private var nextID:Number = 0;
-
- private var lastRef:int;
-
- /* values that can't be serialized natively across the bridge are packed and identified by type.
- These constants represent different serialization types */
- public static const TYPE_ASINSTANCE:uint = 1;
- public static const TYPE_ASFUNCTION:uint = 2;
- public static const TYPE_JSFUNCTION:uint = 3;
- public static const TYPE_ANONYMOUS:uint = 4;
-
- private var _initChecked:Boolean = false;
-
- // properties
-
- //getters and setters for the main component in the swf - the root
- public function get rootObject():DisplayObject {return _rootObject;}
- public function set rootObject(value:DisplayObject):void
- {
- _rootObject = value;
- checkInitialized();
- }
-
- /**
- * the bridge name
- */
- public var bridgeName:String;
- private var _registerComplete:Boolean = false;
-
- /**
- * increment the reference count for an object being passed over the bridge
- */
- public function incRef(objId:int):void
- {
- if(refMap[objId] == null) {
- //the object is being created; we now add it to the map and set its refCount = 1
- refMap[objId] = 1;
- } else {
- refMap[objId] = refMap[objId] +1;
- }
- }
-
- /**
- * when an object has been completely passed to JS its reference count is decreased with 1
- */
- public function releaseRef(objId:int):void
- {
- if(refMap[objId] != null)
- {
- var newRefVal:int = refMap[objId] - 1;
- // if the object exists in the referenceMap and its count equals or has dropped under 0 we clean it up
- if(refMap[objId] != null && newRefVal <= 0)
- {
- delete refMap[objId];
- delete localInstanceMap[objId];
- }
- else
- {
- refMap[objId] = newRefVal;
- }
- }
- }
-
- /**
- * attaches the callbacks to external interface
- */
- public function initializeCallbacks():void
- {
- if (ExternalInterface.available == false)
- {
- return;
- }
-
- ExternalInterface.addCallback("getRoot", js_getRoot);
- ExternalInterface.addCallback("getPropFromAS", js_getPropFromAS);
- ExternalInterface.addCallback("setPropInAS", js_setPropertyInAS);
- ExternalInterface.addCallback("invokeASMethod", js_invokeMethod);
- ExternalInterface.addCallback("invokeASFunction", js_invokeFunction);
- ExternalInterface.addCallback("releaseASObjects", js_releaseASObjects);
- ExternalInterface.addCallback("create", js_create);
- ExternalInterface.addCallback("releaseNamedASObject",js_releaseNamedASObject);
- ExternalInterface.addCallback("incRef", incRef);
- ExternalInterface.addCallback("releaseRef", releaseRef);
- }
-
- private var _rootObject:DisplayObject;
-
- private var _document:DisplayObject;
-
- /**
- * called to check whether the bridge has been initialized for the specified document/id pairs
- */
- public function initialized(document:Object, id:String):void
- {
- _document = (document as DisplayObject);
-
- if (_document != null)
- {
- checkInitialized();
- }
- }
-
- private function get baseObject():DisplayObject
- {
- return (rootObject == null)? _document:rootObject;
- }
-
-
- private function checkInitialized():void
- {
- if(_initChecked== true)
- {
- return;
- }
- _initChecked = true;
-
- // oops! timing error. Player team is working on it.
- var t:Timer = new Timer(200,1);
- t.addEventListener(TimerEvent.TIMER,auxCheckInitialized);
- t.start();
- }
-
- /**
- * auxiliary initialization check that is called after the timing has occurred
- */
- private function auxCheckInitialized(e:Event):void
- {
-
- var bCanGetParams:Boolean = true;
-
- try
- {
- var params:Object = baseObject.root.loaderInfo.parameters;
- }
- catch (e:Error)
- {
- bCanGetParams = false;
- }
-
- if (bCanGetParams == false)
- {
- var t:Timer = new Timer(100);
- var timerFunc:Function = function(e:TimerEvent):void
- {
- if(baseObject.root != null)
- {
- try
- {
- bCanGetParams = true;
- var params:Object = baseObject.root.loaderInfo.parameters;
- }
- catch (err:Error)
- {
- bCanGetParams = false;
- }
- if (bCanGetParams)
- {
- t.removeEventListener(TimerEvent.TIMER, timerFunc);
- t.stop();
- dispatchInit();
- }
- }
- }
- t.addEventListener(TimerEvent.TIMER, timerFunc);
- t.start();
- }
- else
- {
- dispatchInit();
- }
- }
-
- /**
- * call into JS to annunce that the bridge is ready to be used
- */
- private function dispatchInit(e:Event = null):void
- {
- if(_registerComplete == true)
- {
- return;
- }
-
- if (ExternalInterface.available == false)
- {
- return;
- }
-
- if (bridgeName == null)
- {
- bridgeName = baseObject.root.loaderInfo.parameters["bridgeName"];
-
- if(bridgeName == null)
- {
- bridgeName = "flash";
- }
- }
-
- _registerComplete = ExternalInterface.call("FABridge__bridgeInitialized", [bridgeName]);
- dispatchEvent(new Event(FABridge.INITIALIZED));
- }
-
- // serialization/deserialization
-
- /** serializes a value for transfer across the bridge. primitive types are left as is. Arrays are left as arrays, but individual
- * values in the array are serialized according to their type. Functions and class instances are inserted into a hash table and sent
- * across as keys into the table.
- *
- * For class instances, if the instance has been sent before, only its id is passed. If This is the first time the instance has been sent,
- * a ref descriptor is sent associating the id with a type string. If this is the first time any instance of that type has been sent
- * across, a descriptor indicating methods, properties, and variables of the type is also sent across
- */
- public function serialize(value:*, keep_refs:Boolean=false):*
- {
- var result:* = {};
- result.newTypes = [];
- result.newRefs = {};
-
- if (value is Number || value is Boolean || value is String || value == null || value == undefined || value is int || value is uint)
- {
- result = value;
- }
- else if (value is Array)
- {
- result = [];
- for(var i:int = 0; i < value.length; i++)
- {
- result[i] = serialize(value[i], keep_refs);
- }
- }
- else if (value is Function)
- {
- // serialize a class
- result.type = TYPE_ASFUNCTION;
- result.value = getFunctionID(value, true);
- }
- else if (getQualifiedClassName(value) == "Object")
- {
- result.type = TYPE_ANONYMOUS;
- result.value = value;
- }
- else
- {
- // serialize a class
- result.type = TYPE_ASINSTANCE;
- // make sure the type info is available
- var className:String = getQualifiedClassName(value);
-
- var serializer:Function = customSerializersMap[className];
-
- // try looking up the serializer under an alternate name
- if (serializer == null)
- {
- if (className.indexOf('$') > 0)
- {
- var split:int = className.lastIndexOf(':');
- if (split > 0)
- {
- var alternate:String = className.substring(split+1);
- serializer = customSerializersMap[alternate];
- }
- }
- }
-
- if (serializer != null)
- {
- return serializer.apply(null, [value, keep_refs]);
- }
- else
- {
- if (retrieveCachedTypeDescription(className, false) == null)
- {
- try
- {
- result.newTypes.push(retrieveCachedTypeDescription(className, true));
- }
- catch(err:Error)
- {
- var interfaceInfo:XMLList = describeType(value).implementsInterface;
- for each (var interf:XML in interfaceInfo)
- {
- className = interf.@type.toString();
- if (retrieveCachedTypeDescription(className, false) == null){
- result.newTypes.push(retrieveCachedTypeDescription(className, true));
- } //end if push new data type
-
- } //end for going through interfaces
- var baseClass:String = describeType(value).@base.toString();
- if (retrieveCachedTypeDescription(baseClass, false) == null){
- result.newTypes.push(retrieveCachedTypeDescription(baseClass, true));
- } //end if push new data type
- }
- }
-
- // make sure the reference is known
- var objRef:Number = getRef(value, false);
- var should_keep_ref:Boolean = false;
- if (isNaN(objRef))
- {
- //create the reference if necessary
- objRef = getRef(value, true);
- should_keep_ref = true;
- }
-
- result.newRefs[objRef] = className;
- //trace("serializing new reference: " + className + " with value" + value);
-
- //the result is a getProperty / invokeMethod call. How can we know how much you will need the object ?
- if (keep_refs && should_keep_ref) {
- incRef(objRef);
- }
- result.value = objRef;
- }
- }
- return result;
- }
-
- /**
- * deserializes a value passed in from javascript. See serialize for details on how values are packed and
- * unpacked for transfer across the bridge.
- */
- public function deserialize(valuePackage:*):*
- {
- var result:*;
- if (valuePackage is Number || valuePackage is Boolean || valuePackage is String || valuePackage === null || valuePackage === undefined || valuePackage is int || valuePackage is uint)
- {
- result = valuePackage;
- }
- else if(valuePackage is Array)
- {
- result = [];
- for (var i:int = 0; i < valuePackage.length; i++)
- {
- result[i] = deserialize(valuePackage[i]);
- }
- }
- else if (valuePackage.type == FABridge.TYPE_JSFUNCTION)
- {
- result = getRemoteFunctionProxy(valuePackage.value, true);
- }
- else if (valuePackage.type == FABridge.TYPE_ASFUNCTION)
- {
- throw new Error("as functions can't be passed back to as yet");
- }
- else if (valuePackage.type == FABridge.TYPE_ASINSTANCE)
- {
- result = resolveRef(valuePackage.value);
- }
- else if (valuePackage.type == FABridge.TYPE_ANONYMOUS)
- {
- result = valuePackage.value;
- }
- return result;
- }
-
- public function addCustomSerialization(className:String, serializationFunction:Function):void
- {
- customSerializersMap[className] = serializationFunction;
- }
-
-
- // type management
-
- /**
- * retrieves a type description for the type indicated by className, building one and caching it if necessary
- */
- public function retrieveCachedTypeDescription(className:String, createifNecessary:Boolean):Object
- {
- if(localTypeMap[className] == null && createifNecessary == true)
- {
- localTypeMap[className] = buildTypeDescription(className);
- }
- return localTypeMap[className];
- }
-
- public function addCachedTypeDescription(className:String, desc:Object):Object
- {
- if (localTypeMap[className] == null)
- {
- localTypeMap[className] = desc;
- }
- return localTypeMap[className];
- }
-
- /**
- * builds a type description for the type indiciated by className
- */
- public function buildTypeDescription(className:String):Object
- {
- var desc:Object = {};
-
- className = className.replace(/::/,".");
-
- var objClass:Class = Class(ApplicationDomain.currentDomain.getDefinition(className));
-
- var xData:XML = describeType(objClass);
-
- desc.name = xData.@name.toString();
-
- var methods:Array = [];
- var xMethods:XMLList = xData.factory.method;
- for (var i:int = 0; i < xMethods.length(); i++)
- {
- methods.push(xMethods[i].@name.toString());
- }
- desc.methods = methods;
-
- var accessors:Array = [];
- var xAcc:XMLList = xData.factory.accessor;
- for (i = 0; i < xAcc.length(); i++)
- {
- accessors.push(xAcc[i].@name.toString());
- }
- xAcc = xData.factory.variable;
- for (i = 0; i < xAcc.length(); i++)
- {
- accessors.push(xAcc[i].@name.toString());
- }
- desc.accessors = accessors;
-
- return desc;
- }
-
-// instance mgmt
-
- /**
- * resolves an instance id passed from JS to an instance previously cached for representing in JS
- */
- private function resolveRef(objRef:Number):Object
- {
- try
- {
- return (objRef == -1)? baseObject : localInstanceMap[objRef];
- }
- catch(e:Error)
- {
- return serialize("__FLASHERROR__"+"||"+e.message);
- }
-
- return (objRef == -1)? baseObject : localInstanceMap[objRef];
- }
-
- /**
- * returns an id associated with the object provided for passing across the bridge to JS
- */
- public function getRef(obj:Object, createIfNecessary:Boolean):Number
- {
- try
- {
- var ref:Number;
-
- if (createIfNecessary)
- {
- var newRef:Number = nextID++;
- localInstanceMap[newRef] = obj;
- ref = newRef;
- }
- else
- {
- for (var key:* in localInstanceMap)
- {
- if (localInstanceMap[key] === obj)
- {
- ref = key;
- break;
- }
- }
- }
- }
- catch(e:Error)
- {
- return serialize("__FLASHERROR__"+"||"+e.message)
- }
-
- return ref;
- }
-
-
- // function management
-
- /**
- * resolves a function ID passed from JS to a local function previously cached for representation in JS
- */
- private function resolveFunctionID(funcID:Number):Function
- {
- return localFunctionMap[funcID];
- }
-
- /**
- * associates a unique ID with a local function suitable for passing across the bridge to proxy in Javascript
- */
- public function getFunctionID(f:Function, createIfNecessary:Boolean):Number
- {
- var ref:Number;
-
- if (createIfNecessary)
- {
- var newID:Number = nextID++;
- localFunctionMap[newID] = f;
- ref = newID;
- }
- else
- {
- for (var key:* in localFunctionMap)
- {
- if (localFunctionMap[key] === f) {
- ref = key;
- }
- break;
- }
- }
-
- return ref;
- }
-
- /**
- * returns a proxy function that represents a function defined in javascript. This function can be called syncrhonously, and will
- * return any values returned by the JS function
- */
- public function getRemoteFunctionProxy(functionID:Number, createIfNecessary:Boolean):Function
- {
- try
- {
- if (remoteFunctionCache[functionID] == null)
- {
- remoteFunctionCache[functionID] = function(...args):*
- {
- var externalArgs:Array = args.concat();
- externalArgs.unshift(functionID);
- var serializedArgs:* = serialize(externalArgs, true);
-
- if(checkToThrowLater(serializedArgs[1]))
- {
- setTimeout(function a():* {
- try {
- var retVal:* = ExternalInterface.call("FABridge__invokeJSFunction", serializedArgs);
- for(var i:int = 0; i<serializedArgs.length; i++)
- {
- if(typeof(serializedArgs[i]) == "object" && serializedArgs[i]!=null)
- {
- releaseRef(serializedArgs[i].value);
- }
- }
- return retVal;
- }
- catch(e:Error)
- {
- return serialize("__FLASHERROR__"+"||"+e.message);
- }
- },1);
- }
- else
- {
- var retVal:* = ExternalInterface.call("FABridge__invokeJSFunction", serializedArgs);
- for(var i:int = 0; i<serializedArgs.length; i++)
- {
- if(typeof(serializedArgs[i]) == "object" && serializedArgs[i]!=null)
- {
- releaseRef(serializedArgs[i].value);
- }
- }
- return retVal;
- }
- }
- }
- }
- catch(e:Error)
- {
- return serialize("__FLASHERROR__"+"||"+e.message);
- }
-
- return remoteFunctionCache[functionID];
- }
-
- /**
- * function that checks if the object on which we are working demands that it should be called at a later time, breaking the call chain
- * we check the actual object, as well as the bsae class and interfaces
- */
- private function checkToThrowLater(obj:Object):Boolean
- {
- obj = resolveRef(obj.value);
- var className:String = getQualifiedClassName(obj);
- var classInfo:XML = describeType(obj);
-
- if (FABridge.EventsToCallLater[className] != null) {
- return true;
- }
-
- //check if this class doesn't inherit from one of the entries in the table
- var inheritanceInfo:XMLList = describeType(obj).extendsClass;
- for each (var inherit:XML in inheritanceInfo)
- {
- className = inherit.@type.toString();
- if (FABridge.EventsToCallLater[className] != null) {
- return true;
- }
- } //end for going through inheritance tree
-
- //if we're still here, check the interfaces as well
-
- var interfaceInfo:XMLList = describeType(obj).implementsInterface;
- for each (var interf:XML in interfaceInfo)
- {
- className = interf.@type.toString();
- if (FABridge.EventsToCallLater[className] != null) {
- return true;
- }
- } //end for going through inheritance tree
-
- //if nothing was found, return false, so the function gets executed
- return false;
- }
-
- // callbacks exposed to JS
-
- /**
- * called to fetch a named property off the instanced associated with objID
- */
- public function js_getPropFromAS(objID:Number, propName:String):*
- {
- incRef(objID);
- try
- {
- var obj:Object = resolveRef(objID);
- var ret:* = serialize(obj[propName], true);
- releaseRef(objID);
- return ret;
- }
- catch (e:ItemPendingError)
- {
- releaseRef(objID);
- //ItemPendingError
- //return serialize("an error occcured with" + obj[propName]);
- }
- catch(e:Error)
- {
- releaseRef(objID);
- return serialize("__FLASHERROR__" + "||" + e.message);
- }
- }
-
- /**
- * called to set a named property on the instance associated with objID
- */
- private function js_setPropertyInAS(objID:Number, propRef:String, value:*):*
- {
- incRef(objID);
- try {
- var obj:Object = resolveRef(objID);
- obj[propRef] = deserialize(value);
- releaseRef(objID);
- }
- catch(e:Error)
- {
- releaseRef(objID);
- return serialize("__FLASHERROR__" + "||" + e.message);
- }
- }
-
- /**
- * accessor for retrieveing a proxy to the root object from JS
- */
- private function js_getRoot():*
- {
- try
- {
- //always get the root; this is the same as the get property, only it is the root object
- var objRef:Number = getRef(baseObject, false);
- if (isNaN(objRef))
- {
- //create the reference if necessary
- objRef = getRef(baseObject, true);
- incRef(objRef);
- }
- return serialize(baseObject);
- }
- catch(e:Error)
- {
- return serialize("__FLASHERROR__"+"||"+e.message);
- }
- }
-
- /**
- * called to invoke a function or closure associated with funcID
- */
- private function js_invokeFunction(funcID:Number, args:Object):*
- {
- var result:*;
- try
- {
- var func:Function = resolveFunctionID(funcID);
- if(func != null)
- result = func.apply(null, deserialize(args));
-
- return serialize(result, true);
- }
- catch(e:Error)
- {
- return serialize("__FLASHERROR__"+"||"+e.message);
- }
- }
-
- /**
- * called to invoke a named method on the object associated with objID
- */
- private function js_invokeMethod(objID:Number, methodName:String, args:Object):*
- {
- incRef(objID);
- try
- {
- var obj:Object = resolveRef(objID);
- var result:*;
-
- //check if the method is callable right now, or later
- var callLater:Boolean = checkToExecuteLater(obj, methodName);
-
- if (callLater) {
- var t:Timer = new Timer(200, 1);
- t.addEventListener(TimerEvent.TIMER, function():void {
- var ret_inner:* = serialize(obj[methodName].apply(null, deserialize(args)), true);
- releaseRef(objID);
- });
- t.start();
- } else {
- var ret:* = serialize(obj[methodName].apply(null, deserialize(args)), true);
- releaseRef(objID);
- return ret;
- }
- }
- catch (e:ItemPendingError)
- {
- releaseRef(objID);
- // ignore ItemPendingError
- }
- catch(e:Error)
- {
- releaseRef(objID);
- return serialize("__FLASHERROR__" + "||" + e.message);
- }
- }
-
- /**
- * method that performs a check on the specified object and method to see if their execution should be delayed or not
- * it checks the object, its base class and implemented interfaces
- */
- private function checkToExecuteLater(obj:Object, methodName:String):Boolean
- {
- var methods:String;
- var className:String = getQualifiedClassName(obj);
- var classInfo:XML = describeType(obj);
-
- if (FABridge.MethodsToCallLater[className] != null) {
- methods = FABridge.MethodsToCallLater[className];
- //must call later
- if(methods.match(methodName))
- {
- return true;
- }
- }
-
- //check if this class doesn't inherit from one of the entries in the table
- var inheritanceInfo:XMLList = describeType(obj).extendsClass;
- for each (var inherit:XML in inheritanceInfo)
- {
- className = inherit.@type.toString();
- if (FABridge.MethodsToCallLater[className] != null) {
- methods = FABridge.MethodsToCallLater[className];
- //must call later
- if(methods.match(methodName))
- {
- return true;
- }
- }
- } //end for going through inheritance tree
-
- //if we're still here, check the interfaces as well
-
- var interfaceInfo:XMLList = describeType(obj).implementsInterface;
- for each (var interf:XML in interfaceInfo)
- {
- className = interf.@type.toString();
- if (FABridge.MethodsToCallLater[className] != null) {
- methods = FABridge.MethodsToCallLater[className];
- //must call later
- if(methods.match(methodName))
- {
- return true;
- }
- }
- } //end for going through inheritance tree
-
- //if nothing was found, return false, so the function gets executed
- return false;
- }
-
- /**
- * callback from JS to release all AS Objects from the local cache maps
- */
- private function js_releaseASObjects():void
- {
- localTypeMap = new Dictionary();
- localInstanceMap = new Dictionary();
- localFunctionMap = new Dictionary();
- }
-
- /**
- * callback from JS to release a specific object, identified by its ID
- */
- private function js_releaseNamedASObject(objId:int):Boolean
- {
- var retVal:Boolean = false;
- if (localInstanceMap[objId] != null)
- {
- delete refMap[objId];
- delete localInstanceMap[objId];
- retVal = true;
- }
- return retVal;
- }
-
- /**
- * callback for js to create a new class instance.
- */
-
- private function js_create(className:String):*
- {
- try
- {
- var c:Class = Class(ApplicationDomain.currentDomain.getDefinition(className));
- var instance:Object = new c();
- }
- catch(e:Error)
- {
- return serialize("__FLASHERROR__" + "||" + e.message);
- }
-
- // make sure the reference is known
- var objRef:Number = getRef(instance, true);
- incRef(objRef);
- return serialize(instance);
- }
-
-}
-}
diff --git a/sample.html b/sample.html
index 6a2ad7f..8ac4dca 100644..100755
--- a/sample.html
+++ b/sample.html
@@ -8,7 +8,6 @@
<!-- 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">
diff --git a/web_socket.js b/web_socket.js
index b149a80..3b39516 100644..100755
--- a/web_socket.js
+++ b/web_socket.js
@@ -5,373 +5,502 @@
(function() {
- if (window.WebSocket) return;
-
- var console = window.console;
- 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;
- }
- if (location.protocol == "file:") {
- console.error(
- "WARNING: web-socket-js doesn't work in file:///... URL " +
- "unless you set Flash Security Settings properly. " +
- "Open the page via Web server i.e. http://...");
- }
-
- WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
- var self = this;
- self.readyState = WebSocket.CONNECTING;
- self.bufferedAmount = 0;
- // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
- // Otherwise, when onopen fires immediately, onopen is called before it is set.
- setTimeout(function() {
- WebSocket.__addTask(function() {
- self.__createFlash(url, protocol, proxyHost, proxyPort, headers);
- });
- }, 0);
- };
+ if (window.WebSocket) return;
+
+ var console = window.console;
+ 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;
+ }
+ if (location.protocol == "file:") {
+ console.error(
+ "WARNING: web-socket-js doesn't work in file:///... URL " +
+ "unless you set Flash Security Settings properly. " +
+ "Open the page via Web server i.e. http://...");
+ }
- 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("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);
- });
- //console.log("[WebSocket] Flash object is ready");
- };
+ WebSocketController = function() {
+ this._webSockets = {};
+ this._initializing = false;
+ this._initialized = false;
+ this._currentWebSocketId = 0;
+ this._flashObj = null;
+ };
+
+ /**
+ * Load and initialize the flash
+ * mock web socket.
+ */
+ WebSocketController.prototype.init = function() {
+ if (this._initialized || this._initializing) {
+ return;
+ }
+ this._initializing = true;
+
+ if (WebSocket.__swfLocation) {
+ // For backword compatibility.
+ window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
+ }
+ if (!window.WEB_SOCKET_SWF_LOCATION) {
+ console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
+ return;
+ }
+ var container = document.createElement("div");
+ container.id = "webSocketContainer";
+ // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
+ // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
+ // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
+ // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
+ // the best we can do as far as we know now.
+ container.style.position = "absolute";
+ if (this.__isFlashLite()) {
+ container.style.left = "0px";
+ container.style.top = "0px";
+ } else {
+ container.style.left = "-100px";
+ container.style.top = "-100px";
+ }
+ var holder = document.createElement("div");
+ holder.id = "webSocketFlash";
+ container.appendChild(holder);
+ document.body.appendChild(container);
+ // See this article for hasPriority:
+ // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
+ swfobject.embedSWF(
+ WEB_SOCKET_SWF_LOCATION,
+ "webSocketFlash",
+ "1" /* width */,
+ "1" /* height */,
+ "10.0.0" /* SWF version */,
+ null,
+ null,
+ {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
+ null,
+ function(e) {
+ if (!e.success) {
+ console.error("[WebSocket] swfobject.embedSWF failed");
+ }
+ });
+ };
+
+ /**
+ * Called by flash to notify js that it's fully loaded and ready
+ * for communication.
+ */
+ WebSocketController.prototype.flashInitialized = function() {
+ //We need to set a timeout here to avoid round-trip calls
+ //to flash during the initialization process.
+ setTimeout(function() {
+ wsController._onFlashInitialized();
+ }, 0);
+ };
+
+ /**
+ * Method called once flash is ready for communication.
+ */
+ WebSocketController.prototype._onFlashInitialized = function() {
+ var webSocket;
+
+ this._initialized = true;
+ this._flashObj = document.getElementById('webSocketFlash');
+ this._flashObj.setCallerUrl(location.href);
+ this._flashObj.setDebug(!!window.WEB_SOCKET_DEBUG);
+
+ //If we have any policy files waiting to be loaded, load them.
+ if (this._policyUrls) {
+ var i = this._policyUrls.length;
+ while (i-->0) {
+ this._flashObj.loadFlashPolicyFile(this._policyUrls[i]);
+ }
+ }
+
+ //If we have any websockets ready to go, initialize them.
+ for (var webSocketId in this._webSockets) {
+ webSocket = this._webSockets[webSocketId];
+ this._flashObj.createWebSocket(webSocketId, webSocket.url, webSocket.protocol, webSocket.proxyHost, webSocket.proxyPort, webSocket.headers);
+ }
+ };
+
+ /**
+ * Create a new websocket instance in flash and send the new web socket id back
+ * to the caller.
+ * @param {WebSocket} webSocket The websocket instance handling this web socket.
+ * @return {int} The new flash web socket id.
+ */
+ WebSocketController.prototype.createFlashWebSocket = function(webSocket) {
+ var webSocketId = this._currentWebSocketId++;
+
+ this._webSockets[webSocketId] = webSocket;
+
+ //If the flash component has been initialized, open the web socket connection
+ if (this._initialized) {
+ this._flashObj.createWebSocket(webSocketId, webSocket.url, webSocket.protocol, webSocket.proxyHost, webSocket.proxyPort, webSocket.headers);
+ } else if (!this._initializing) {
+ //Load flash swf and initialize
+ this.init();
+ }
+
+ return webSocketId;
+ };
+
+ /**
+ * Send data through the flash socket.
+ * @param {int} socketId ID of the web socket to use.
+ * @param {string} data Data to send.
+ */
+ WebSocketController.prototype.send = function(socketId, data) {
+ return this._flashObj.send(socketId, data);
+ };
+
+
+ /**
+ * Close the flash socket.
+ * @param {int} socketId ID of the web socket to use.
+ */
+ WebSocketController.prototype.close = function(socketId) {
+ return this._flashObj.close(socketId);
+ };
+
+ /**
+ * Test if the browser is running flash lite.
+ * @return {boolean} True if flash lite is running, false otherwise.
+ */
+ WebSocketController.prototype.__isFlashLite = function() {
+ if (!window.navigator || !window.navigator.mimeTypes) {
+ return false;
+ }
+ var mimeType = window.navigator.mimeTypes['application/x-shockwave-flash'];
+ if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
+ return false;
+ }
+ return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
+ };
+
+ /**
+ * Called by flash to dispatch an event to a web socket.
+ * @param {object} eventObj A web socket event dispatched from flash.
+ */
+ WebSocketController.prototype.flashEvent = function(eventObj) {
+ var socketId = eventObj.webSocketId,
+ wsEvent = new WebSocketEvent();
+
+ wsEvent.initEvent(eventObj.type, true, true);
+ wsEvent.readyState = eventObj.readyState;
+ wsEvent.message = eventObj.message;
+
+ setTimeout(function() {
+ wsController.dispatchEvent(socketId, wsEvent);
+ }, 0);
+
+ return true;
+ };
+
+ WebSocketController.prototype.dispatchEvent = function(webSocketId, wsEvent) {
+ this._webSockets[webSocketId].__handleEvent(wsEvent);
+ };
+
+ /**
+ * Load a new flash security policy file.
+ * @param {string} url
+ */
+ WebSocketController.prototype.loadFlashPolicyFile = function(url){
+ if (this._initialized) {
+ this._flashObj.loadFlashPolicyFile(url);
+ }
+ else {
+ this._policyUrls = this._policyUrls || [];
+ this._policyUrls.push(url);
+ }
+ };
- WebSocket.prototype.send = function(data) {
- if (!this.__flash || this.readyState == WebSocket.CONNECTING) {
- throw "INVALID_STATE_ERR: Web Socket connection has not been established";
- }
- // We use encodeURIComponent() here, because FABridge doesn't work if
- // the argument includes some characters. We don't use escape() here
- // because of this:
- // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
- // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
- // preserve all Unicode characters either e.g. "\uffff" in Firefox.
- var result = this.__flash.send(encodeURIComponent(data));
- if (result < 0) { // success
- return true;
- } else {
- this.bufferedAmount += result;
- return false;
- }
- };
- WebSocket.prototype.close = function() {
- var self = this;
- if (!self.__flash) return;
- 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
- // which causes weird error:
- // > You are trying to call recursively into the Flash Player which is not allowed.
- self.readyState = WebSocket.CLOSED;
- if (self.__timer) clearInterval(self.__timer);
- if (self.onclose) {
- // Make it asynchronous so that it looks more like an actual
- // close event
- setTimeout(self.onclose, 0);
- }
- };
- /**
- * 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] = this.__createEventHandler(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';
+ /**
+ * This class represents a faux web socket.
+ * @param {string} url
+ * @param {string} protocol
+ * @param {string} proxyHost
+ * @param {int} proxyPort
+ * @param {string} headers
+ */
+ WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
+ this.url = url;
+ this.protocol = protocol;
+ this.proxyHost = proxyHost;
+ this.headers = headers;
+
+ this.readyState = WebSocket.CONNECTING;
+ this.bufferedAmount = 0;
+ this._socketId = wsController.createFlashWebSocket(this);
+ };
- 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);
- }
- };
-
- 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 events = this.__flash.receiveEvents();
- for (var i = 0; i < events.length; i++) {
- try {
- 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.
- var self = this;
- this.__timer = setInterval(function () {
- self.__handleEvents();
- }, 500);
- }
- if (this.onopen) this.onopen();
-
- } else if (event.type == "close") {
-
- if (this.__timer) clearInterval(this.__timer);
- if (this.onclose) this.onclose();
-
- } else if (event.type == "message") {
-
- if (this.onmessage) {
- var data = decodeURIComponent(event.data);
- var e;
- if (window.MessageEvent && !window.opera) {
- e = document.createEvent("MessageEvent");
- e.initMessageEvent("message", false, false, data, null, null, window, null);
- } else {
- // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
- e = {data: data};
- }
- this.onmessage(e);
- }
-
- } 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
- */
- WebSocket.prototype.__createEventHandler = function(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);
- };
- };
+ /**
+ * Send data to the web socket.
+ * @param {string} data The data to send to the socket.
+ * @return {boolean} True for success, false for failure.
+ */
+ WebSocket.prototype.send = function(data) {
+ if (this.readyState == WebSocket.CONNECTING) {
+ throw "INVALID_STATE_ERR: Web Socket connection has not been established";
+ }
+ // We use encodeURIComponent() here, because FABridge doesn't work if
+ // the argument includes some characters. We don't use escape() here
+ // because of this:
+ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
+ // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
+ // preserve all Unicode characters either e.g. "\uffff" in Firefox.
+ // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
+ // additional testing.
+ var result = wsController.send(this._socketId, encodeURIComponent(data));
+ if (result < 0) { // success
+ return true;
+ } else {
+ this.bufferedAmount += result;
+ return false;
+ }
+ };
- /**
- * 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(){}
+ /**
+ * Close this web socket gracefully.
+ */
+ WebSocket.prototype.close = function() {
+ if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
+ return;
+ }
+ this.readyState = WebSocket.CLOSING;
+ wsController.close(this._socketId);
+ };
- /**
- *
- * @type boolean
- */
- WebSocketEvent.prototype.cancelable = true;
+ /**
+ * 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] = this.__createEventHandler(this, type);
+ }
+ }
+ this.__events[type].push(listener);
+ };
- /**
- *
- * @type boolean
- */
- WebSocketEvent.prototype.cancelBubble = false;
+ /**
+ * 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;
+ }
+ }
+ };
- /**
- *
- * @return void
- */
- WebSocketEvent.prototype.preventDefault = function() {
- if (this.cancelable) {
- this.returnValue = false;
- }
- };
+ /**
+ * 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);
+ }
+ };
- /**
- *
- * @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.CLOSING = 2;
- WebSocket.CLOSED = 3;
+ /**
+ * Handle an event from flash. Do any websocket-specific
+ * handling before passing the event off to the event handlers.
+ * @param {Object} event
+ */
+ WebSocket.prototype.__handleEvent = function(event) {
+ // 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.
+ if (event.readyState >= 0) {
+ this.readyState = event.readyState;
+ }
+
+ try {
+ if (event.type == "open" && this.onopen) {
+ this.onopen();
+ } else if (event.type == "close" && this.onclose) {
+ this.onclose();
+ } else if (event.type == "error" && this.onerror) {
+ this.onerror(event);
+ } else if (event.type == "message") {
+ if (this.onmessage) {
+ var data = decodeURIComponent(event.message);
+ 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 {
+ throw "unknown event type: " + event.type;
+ }
+ } catch (e) {
+ console.error(e.toString());
+ }
+ };
+
+ /**
+ * @param {object} object
+ * @param {string} type
+ */
+ WebSocket.prototype.__createEventHandler = function(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);
+ };
+ };
- WebSocket.__tasks = [];
+ /**
+ * Load the specified flash security policy file.
+ * NOTE: This should be called prior to instantiating any WebSockets.
+ * @param {string} url URL to the remote policy file.
+ */
+ WebSocket.loadFlashPolicyFile = function(url) {
+ wsController.loadFlashPolicyFile(url);
+ };
- WebSocket.loadFlashPolicyFile = function(url) {
- WebSocket.__addTask(function() {
- WebSocket.__flash.loadManualPolicyFile(url);
- });
- }
+ /**
+ * Define the WebSocket readyState enumeration.
+ */
+ WebSocket.CONNECTING = 0;
+ WebSocket.OPEN = 1;
+ WebSocket.CLOSING = 2;
+ WebSocket.CLOSED = 3;
- WebSocket.__initialize = function() {
- if (WebSocket.__swfLocation) {
- // For backword compatibility.
- window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
- }
- if (!window.WEB_SOCKET_SWF_LOCATION) {
- console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
- return;
- }
- var container = document.createElement("div");
- container.id = "webSocketContainer";
- // Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
- // Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
- // But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
- // Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
- // the best we can do as far as we know now.
- container.style.position = "absolute";
- if (WebSocket.__isFlashLite()) {
- container.style.left = "0px";
- container.style.top = "0px";
- } else {
- container.style.left = "-100px";
- container.style.top = "-100px";
- }
- var holder = document.createElement("div");
- holder.id = "webSocketFlash";
- container.appendChild(holder);
- document.body.appendChild(container);
- // See this article for hasPriority:
- // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
- swfobject.embedSWF(
- WEB_SOCKET_SWF_LOCATION, "webSocketFlash",
- "1" /* width */, "1" /* height */, "9.0.0" /* SWF version */,
- null, {bridgeName: "webSocket"}, {hasPriority: true, allowScriptAccess: "always"}, 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);
- WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
- for (var i = 0; i < WebSocket.__tasks.length; ++i) {
- WebSocket.__tasks[i]();
- }
- WebSocket.__tasks = [];
- } catch (e) {
- console.error("[WebSocket] " + e.toString());
- }
- });
- };
+ /**
+ * 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 int WebSocket state enumeration
+ */
+ WebSocketEvent.prototype.readyState = -1;
+
+ /**
+ * @type string message received from the flash socket.
+ */
+ WebSocketEvent.prototype.message = null;
+
+ /**
+ *
+ * @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;
+ };
- WebSocket.__addTask = function(task) {
- if (WebSocket.__flash) {
- task();
- } else {
- WebSocket.__tasks.push(task);
- }
- };
-
- WebSocket.__isFlashLite = function() {
- if (!window.navigator || !window.navigator.mimeTypes) return false;
- var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
- if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) return false;
- return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
- };
+ /**
+ *
+ * @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();
+ };
- // called from Flash
- window.webSocketLog = function(message) {
- console.log(decodeURIComponent(message));
- };
- // called from Flash
- window.webSocketError = function(message) {
- console.error(decodeURIComponent(message));
- };
+ window.wsController = new WebSocketController();
- if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
- if (window.addEventListener) {
- window.addEventListener("load", WebSocket.__initialize, false);
- } else {
- window.attachEvent("onload", WebSocket.__initialize);
- }
- }
+ // called from Flash
+ window.webSocketLog = function(message) {
+ console.log(decodeURIComponent(message));
+ };
+
+ // called from Flash
+ window.webSocketError = function(message) {
+ console.error(decodeURIComponent(message));
+ };
+
+ if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
+ if (window.addEventListener) {
+ window.addEventListener("load", function(){
+ wsController.init();
+ }, false);
+ } else {
+ window.attachEvent("onload", function(){
+ wsController.init();
+ });
+ }
+ }
})();