From eeedd74380ff3f3a9f9a71777e361537058701f1 Mon Sep 17 00:00:00 2001 From: Wtritch Date: Tue, 22 Feb 2011 15:45:26 -0800 Subject: Completed updates: Removed FABridge dependence. Created javascript WebSocketController to manage multiple instances. Created central ExternalInterface manager at flash-src/bridge/JSBridge.as --- FABridge.js | 604 --------------------------- WebSocketMain.swf | Bin 180028 -> 252352 bytes WebSocketMainInsecure.zip | Bin 172823 -> 245413 bytes flash-src/WebSocket.as | 67 +-- flash-src/WebSocketMain.as | 225 +++++++---- flash-src/bridge/FABridge.as | 943 ------------------------------------------- sample.html | 1 - web_socket.js | 827 +++++++++++++++++++++---------------- 8 files changed, 657 insertions(+), 2010 deletions(-) delete mode 100644 FABridge.js mode change 100644 => 100755 WebSocketMain.swf delete mode 100644 flash-src/bridge/FABridge.as mode change 100644 => 100755 sample.html mode change 100644 => 100755 web_socket.js 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 old mode 100644 new mode 100755 index 694f9dc..123b170 Binary files a/WebSocketMain.swf and b/WebSocketMain.swf differ diff --git a/WebSocketMainInsecure.zip b/WebSocketMainInsecure.zip index ad3c9b9..99b6f66 100644 Binary files a/WebSocketMainInsecure.zip and b/WebSocketMainInsecure.zip 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 -