From d920595453ee6f1174d2e3ba598b2e82d013b637 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Sat, 17 Apr 2010 17:14:33 -0500 Subject: [PATCH] Import web-socket-js: a0fb3933ce5c824bcb882f5a1cf87e46de773ea8 web-socket-js is a flash based WebSockets emulator. From: http://github.com/gimite/web-socket-js --- include/web-socket-js/FABridge.js | 604 +++++++++++ include/web-socket-js/README.txt | 78 ++ include/web-socket-js/WebSocketMain.swf | Bin 0 -> 11791 bytes include/web-socket-js/flash-src/WebSocket.as | 254 +++++ .../web-socket-js/flash-src/WebSocketMain.as | 83 ++ .../flash-src/WebSocketMessageEvent.as | 30 + .../flash-src/WebSocketStateEvent.as | 32 + .../flash-src/bridge/FABridge.as | 943 ++++++++++++++++++ .../com/adobe/net/proxies/RFC2817Socket.as | 204 ++++ include/web-socket-js/sample.html | 70 ++ include/web-socket-js/swfobject.js | 4 + include/web-socket-js/web_socket.js | 311 ++++++ 12 files changed, 2613 insertions(+) create mode 100644 include/web-socket-js/FABridge.js create mode 100644 include/web-socket-js/README.txt create mode 100644 include/web-socket-js/WebSocketMain.swf create mode 100644 include/web-socket-js/flash-src/WebSocket.as create mode 100644 include/web-socket-js/flash-src/WebSocketMain.as create mode 100644 include/web-socket-js/flash-src/WebSocketMessageEvent.as create mode 100644 include/web-socket-js/flash-src/WebSocketStateEvent.as create mode 100644 include/web-socket-js/flash-src/bridge/FABridge.as create mode 100644 include/web-socket-js/flash-src/com/adobe/net/proxies/RFC2817Socket.as create mode 100644 include/web-socket-js/sample.html create mode 100644 include/web-socket-js/swfobject.js create mode 100755 include/web-socket-js/web_socket.js diff --git a/include/web-socket-js/FABridge.js b/include/web-socket-js/FABridge.js new file mode 100644 index 0000000..971ac35 --- /dev/null +++ b/include/web-socket-js/FABridge.js @@ -0,0 +1,604 @@ +/* +/* +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) + { + 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/include/web-socket-js/README.txt b/include/web-socket-js/README.txt new file mode 100644 index 0000000..0ac9a89 --- /dev/null +++ b/include/web-socket-js/README.txt @@ -0,0 +1,78 @@ +* How to try + +Assuming you have Web server (e.g. Apache) running at http://example.com/ . + +- Download web_socket.rb from: + http://github.com/gimite/web-socket-ruby/tree/master +- Run sample Web Socket server (echo server) in example.com with: (#1) + $ ruby web-socket-ruby/samples/echo_server.rb example.com 10081 +- If your server already provides socket policy file at port 843, modify the file to allow access to port 10081. Otherwise you can skip this step. See below for details. +- Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html). +- Change ws://localhost:10081 to ws://example.com:10081 in sample.html. +- Open sample.html in your browser. +- After "onopen" is shown, input something, click [Send] and confirm echo back. + +#1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com. + + +* How to debug + +If sample.html doesn't work, check these: + +- It doesn't work when you open sample.html as local file i.e. file:///.../sample.html. Open it via Web server. +- Make sure port 10081 is not blocked by your server/client's firewall. +- Use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors. +- Install debugger version of Flash Player available here to see Flash errors: +http://www.adobe.com/support/flashplayer/downloads.html + + +* Supported environment + +I confirmed it works on Chrome 3, Firefox 3.5 and IE 8. It may not work in other browsers. +It requires Flash Player 9 or later (probably). + +On Chrome 4 Dev Channel, it just uses native Web Socket implementation. + + +* Flash socket policy file + +This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash. + +If you use web-socket-ruby available at +http://github.com/gimite/web-socket-ruby/tree/master +, you don't need anything special, because web-socket-ruby handles Flash socket policy file request. But if you already provide socket policy file at port 843, you need to modify the file to allow access to Web Socket port, because it precedes what web-socket-ruby provides. + +If you use other Web Socket server implementation, you need to provide socket policy file yourself. See +http://www.lightsphere.com/dev/articles/flash_socket_policy.html +for details and sample script to run socket policy file server. + +Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster. + + +* Cookie considerations + +Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard). + +Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin. + + +* Proxy considerations + +The WebSocket spec (http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method. + +The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy. + +The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails. + + +* How to build WebSocketMain.swf + +Install Flex SDK. + +$ cd flash-src +$ mxmlc -output=../WebSocketMain.swf WebSocketMain.as + + +* License + +New BSD License. diff --git a/include/web-socket-js/WebSocketMain.swf b/include/web-socket-js/WebSocketMain.swf new file mode 100644 index 0000000000000000000000000000000000000000..33810879d37a0a2a29236b04785daaeaca7c61b0 GIT binary patch literal 11791 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 +// Lincense: New BSD Lincense +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31 + +package { + +import flash.display.*; +import flash.events.*; +import flash.external.*; +import flash.net.*; +import flash.system.*; +import flash.utils.*; +import mx.core.*; +import mx.controls.*; +import mx.events.*; +import mx.utils.*; +import com.adobe.net.proxies.RFC2817Socket; + +[Event(name="message", type="WebSocketMessageEvent")] +[Event(name="open", type="flash.events.Event")] +[Event(name="close", type="flash.events.Event")] +[Event(name="stateChange", type="WebSocketStateEvent")] +public class WebSocket extends EventDispatcher { + + private static var CONNECTING:int = 0; + private static var OPEN:int = 1; + private static var CLOSED:int = 2; + + private var socket:RFC2817Socket; + private var main:WebSocketMain; + private var scheme:String; + private var host:String; + private var port:uint; + private var path:String; + private var origin:String; + private var protocol:String; + private var buffer:ByteArray = new ByteArray(); + private var headerState:int = 0; + private var readyState:int = CONNECTING; + private var bufferedAmount:int = 0; + private var headers:String; + + public function WebSocket( + main:WebSocketMain, url:String, protocol:String, + proxyHost:String = null, proxyPort:int = 0, + headers:String = null) { + this.main = main; + var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?$/); + if (!m) main.fatal("invalid url: " + url); + this.scheme = m[1]; + this.host = m[2]; + this.port = parseInt(m[4] || "80"); + this.path = m[5] || "/"; + this.origin = main.getOrigin(); + this.protocol = protocol; + // if present and not the empty string, headers MUST end with \r\n + // headers should be zero or more complete lines, for example + // "Header1: xxx\r\nHeader2: yyyy\r\n" + this.headers = headers; + + socket = new RFC2817Socket(); + + // if no proxy information is supplied, it acts like a normal Socket + // @see RFC2817Socket::connect + if (proxyHost != null && proxyPort != 0){ + socket.setProxyInfo(proxyHost, proxyPort); + } + + socket.addEventListener(Event.CLOSE, onSocketClose); + socket.addEventListener(Event.CONNECT, onSocketConnect); + socket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError); + socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError); + socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData); + socket.connect(host, port); + } + + public function send(data:String):int { + if (readyState == OPEN) { + socket.writeByte(0x00); + socket.writeUTFBytes(data); + socket.writeByte(0xff); + socket.flush(); + main.log("sent: " + data); + return -1; + } else if (readyState == CLOSED) { + var bytes:ByteArray = new ByteArray(); + bytes.writeUTFBytes(data); + bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff + // We use return value to let caller know bufferedAmount because we cannot fire + // stateChange event here which causes weird error: + // > You are trying to call recursively into the Flash Player which is not allowed. + return bufferedAmount; + } else { + main.fatal("invalid state"); + return 0; + } + } + + public function close():void { + main.log("close"); + try { + socket.close(); + } catch (ex:Error) { } + readyState = CLOSED; + // We don't fire any events here because it causes weird error: + // > You are trying to call recursively into the Flash Player which is not allowed. + // We do something equivalent in JavaScript WebSocket#close instead. + } + + public function getReadyState():int { + return readyState; + } + + public function getBufferedAmount():int { + return bufferedAmount; + } + + private function onSocketConnect(event:Event):void { + main.log("connected"); + var hostValue:String = host + (port == 80 ? "" : ":" + port); + var cookie:String = ""; + if (main.getCallerHost() == host) { + cookie = ExternalInterface.call("function(){return document.cookie}"); + } + var opt:String = ""; + if (protocol) opt += "WebSocket-Protocol: " + protocol + "\r\n"; + // if caller passes additional headers they must end with "\r\n" + if (headers) opt += headers; + + var req:String = StringUtil.substitute( + "GET {0} HTTP/1.1\r\n" + + "Upgrade: WebSocket\r\n" + + "Connection: Upgrade\r\n" + + "Host: {1}\r\n" + + "Origin: {2}\r\n" + + "Cookie: {4}\r\n" + + "{3}" + + "\r\n", + path, hostValue, origin, opt, cookie); + main.log("request header:\n" + req); + socket.writeUTFBytes(req); + socket.flush(); + } + + private function onSocketClose(event:Event):void { + main.log("closed"); + readyState = CLOSED; + notifyStateChange(); + dispatchEvent(new Event("close")); + } + + private function onSocketIoError(event:IOErrorEvent):void { + close(); + main.fatal("failed to connect Web Socket server (IoError)"); + } + + private function onSocketSecurityError(event:SecurityErrorEvent):void { + close(); + main.fatal( + "failed to connect Web Socket server (SecurityError)\n" + + "make sure the server is running and Flash socket policy file is correctly placed"); + } + + private function onSocketData(event:ProgressEvent):void { + var pos:int = buffer.length; + socket.readBytes(buffer, pos); + for (; pos < buffer.length; ++pos) { + if (headerState != 4) { + // try to find "\r\n\r\n" + if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) { + ++headerState; + } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) { + ++headerState; + } else { + headerState = 0; + } + if (headerState == 4) { + var headerStr:String = buffer.readUTFBytes(pos + 1); + main.log("response header:\n" + headerStr); + validateHeader(headerStr); + makeBufferCompact(); + pos = -1; + readyState = OPEN; + notifyStateChange(); + dispatchEvent(new Event("open")); + } + } else { + if (buffer[pos] == 0xff) { + if (buffer.readByte() != 0x00) { + close(); + main.fatal("data must start with \\x00"); + } + var data:String = buffer.readUTFBytes(pos - 1); + main.log("received: " + data); + dispatchEvent(new WebSocketMessageEvent("message", encodeURIComponent(data))); + buffer.readByte(); + makeBufferCompact(); + pos = -1; + } + } + } + } + + private function validateHeader(headerStr:String):void { + var lines:Array = headerStr.split(/\r\n/); + if (!lines[0].match(/^HTTP\/1.1 101 /)) { + close(); + main.fatal("bad response: " + lines[0]); + } + var header:Object = {}; + for (var i:int = 1; i < lines.length; ++i) { + if (lines[i].length == 0) continue; + var m:Array = lines[i].match(/^(\S+): (.*)$/); + if (!m) { + close(); + main.fatal("failed to parse response header line: " + lines[i]); + } + header[m[1]] = m[2]; + } + if (header["Upgrade"] != "WebSocket") { + close(); + main.fatal("invalid Upgrade: " + header["Upgrade"]); + } + if (header["Connection"] != "Upgrade") { + close(); + main.fatal("invalid Connection: " + header["Connection"]); + } + var resOrigin:String = header["WebSocket-Origin"].toLowerCase(); + if (resOrigin != origin) { + close(); + main.fatal("origin doesn't match: '" + resOrigin + "' != '" + origin + "'"); + } + if (protocol && header["WebSocket-Protocol"] != protocol) { + close(); + main.fatal("protocol doesn't match: '" + + header["WebSocket-Protocol"] + "' != '" + protocol + "'"); + } + } + + private function makeBufferCompact():void { + if (buffer.position == 0) return; + var nextBuffer:ByteArray = new ByteArray(); + buffer.readBytes(nextBuffer); + buffer = nextBuffer; + } + + private function notifyStateChange():void { + dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount)); + } + +} + +} diff --git a/include/web-socket-js/flash-src/WebSocketMain.as b/include/web-socket-js/flash-src/WebSocketMain.as new file mode 100644 index 0000000..1ac33e1 --- /dev/null +++ b/include/web-socket-js/flash-src/WebSocketMain.as @@ -0,0 +1,83 @@ +// Copyright: Hiroshi Ichikawa +// Lincense: New BSD Lincense +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31 + +package { + +import flash.display.*; +import flash.events.*; +import flash.external.*; +import flash.net.*; +import flash.system.*; +import flash.utils.*; +import mx.core.*; +import mx.controls.*; +import mx.events.*; +import mx.utils.*; +import bridge.FABridge; + +public class WebSocketMain extends Sprite { + + private var policyLoaded:Boolean = false; + private var callerUrl:String; + + public function WebSocketMain() { + + // This is to avoid "You are trying to call recursively into the Flash Player ..." + // error which (I heard) happens when you pass bunch of messages. + // This workaround was written here: + // http://www.themorphicgroup.com/blog/2009/02/14/fabridge-error-you-are-trying-to-call-recursively-into-the-flash-player-which-is-not-allowed/ + FABridge.EventsToCallLater["flash.events::Event"] = "true"; + FABridge.EventsToCallLater["WebSocketMessageEvent"] = "true"; + FABridge.EventsToCallLater["WebSocketStateEvent"] = "true"; + + var fab:FABridge = new FABridge(); + fab.rootObject = this; + //log("Flash initialized"); + + } + + public function setCallerUrl(url:String):void { + callerUrl = url; + } + + public function create( + url:String, protocol:String, + proxyHost:String = null, proxyPort:int = 0, + headers:String = null):WebSocket { + loadPolicyFile(null); + return new WebSocket(this, url, protocol, proxyHost, proxyPort, headers); + } + + public function getOrigin():String { + return (URLUtil.getProtocol(this.callerUrl) + "://" + + URLUtil.getServerNameWithPort(this.callerUrl)).toLowerCase(); + } + + public function getCallerHost():String { + return URLUtil.getServerName(this.callerUrl); + } + + public function loadPolicyFile(url:String):void { + if (policyLoaded && !url) return; + if (!url) { + url = "xmlsocket://" + URLUtil.getServerName(this.callerUrl) + ":843"; + } + log("policy file: " + url); + Security.loadPolicyFile(url); + policyLoaded = true; + } + + public function log(message:String):void { + ExternalInterface.call("webSocketLog", encodeURIComponent("[WebSocket] " + message)); + } + + public function fatal(message:String):void { + ExternalInterface.call("webSocketError", encodeURIComponent("[WebSocket] " + message)); + throw message; + } + +} + +} diff --git a/include/web-socket-js/flash-src/WebSocketMessageEvent.as b/include/web-socket-js/flash-src/WebSocketMessageEvent.as new file mode 100644 index 0000000..15b0f8c --- /dev/null +++ b/include/web-socket-js/flash-src/WebSocketMessageEvent.as @@ -0,0 +1,30 @@ +// Copyright: Hiroshi Ichikawa +// Lincense: New BSD Lincense +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31 + +package { + +import flash.display.*; +import flash.events.*; +import flash.external.*; +import flash.net.*; +import flash.system.*; +import flash.utils.*; +import mx.core.*; +import mx.controls.*; +import mx.events.*; +import mx.utils.*; + +public class WebSocketMessageEvent extends Event { + + public var data:String; + + public function WebSocketMessageEvent(type:String, data:String) { + super(type); + this.data = data; + } + +} + +} diff --git a/include/web-socket-js/flash-src/WebSocketStateEvent.as b/include/web-socket-js/flash-src/WebSocketStateEvent.as new file mode 100644 index 0000000..88a81e0 --- /dev/null +++ b/include/web-socket-js/flash-src/WebSocketStateEvent.as @@ -0,0 +1,32 @@ +// Copyright: Hiroshi Ichikawa +// Lincense: New BSD Lincense +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31 + +package { + +import flash.display.*; +import flash.events.*; +import flash.external.*; +import flash.net.*; +import flash.system.*; +import flash.utils.*; +import mx.core.*; +import mx.controls.*; +import mx.events.*; +import mx.utils.*; + +public class WebSocketStateEvent extends Event { + + public var readyState:int; + public var bufferedAmount:int; + + public function WebSocketStateEvent(type:String, readyState:int, bufferedAmount:int) { + super(type); + this.readyState = readyState; + this.bufferedAmount = bufferedAmount; + } + +} + +} diff --git a/include/web-socket-js/flash-src/bridge/FABridge.as b/include/web-socket-js/flash-src/bridge/FABridge.as new file mode 100644 index 0000000..d03dba0 --- /dev/null +++ b/include/web-socket-js/flash-src/bridge/FABridge.as @@ -0,0 +1,943 @@ +/* +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 0) + { + this.dispatchEvent(event); + } + } + this.buffer = null; + } + + private function redirectConnectEvent():void + { + super.removeEventListener(Event.CONNECT, onConnect); + var deferredEventHandler:Object = this.deferredEventHandlers[Event.CONNECT]; + if (deferredEventHandler != null) + { + super.addEventListener(Event.CONNECT, deferredEventHandler.listener, deferredEventHandler.useCapture, deferredEventHandler.priority, deferredEventHandler.useWeakReference); + } + } + + private function redirectSocketDataEvent():void + { + super.removeEventListener(ProgressEvent.SOCKET_DATA, onSocketData); + var deferredEventHandler:Object = this.deferredEventHandlers[ProgressEvent.SOCKET_DATA]; + if (deferredEventHandler != null) + { + super.addEventListener(ProgressEvent.SOCKET_DATA, deferredEventHandler.listener, deferredEventHandler.useCapture, deferredEventHandler.priority, deferredEventHandler.useWeakReference); + } + } + + public override function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int=0.0, useWeakReference:Boolean=false):void + { + if (type == Event.CONNECT || type == ProgressEvent.SOCKET_DATA) + { + this.deferredEventHandlers[type] = {listener:listener,useCapture:useCapture, priority:priority, useWeakReference:useWeakReference}; + } + else + { + super.addEventListener(type, listener, useCapture, priority, useWeakReference); + } + } + } +} \ No newline at end of file diff --git a/include/web-socket-js/sample.html b/include/web-socket-js/sample.html new file mode 100644 index 0000000..a2883b1 --- /dev/null +++ b/include/web-socket-js/sample.html @@ -0,0 +1,70 @@ + + + + + Sample of web_socket.js + + + + + + + + +

+ + + + +
+ diff --git a/include/web-socket-js/swfobject.js b/include/web-socket-js/swfobject.js new file mode 100644 index 0000000..8eafe9d --- /dev/null +++ b/include/web-socket-js/swfobject.js @@ -0,0 +1,4 @@ +/* SWFObject v2.2 + is released under the MIT License +*/ +var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab +// Lincense: New BSD Lincense +// Reference: http://dev.w3.org/html5/websockets/ +// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol + +(function() { + + if (window.WebSocket) return; + + var console = window.console; + if (!console) console = {log: function(){ }, error: function(){ }}; + + function hasFlash() { + if ('navigator' in window && 'plugins' in navigator && navigator.plugins['Shockwave Flash']) { + return !!navigator.plugins['Shockwave Flash'].description; + } + if ('ActiveXObject' in window) { + try { + return !!new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); + } catch (e) {} + } + return false; + } + + if (!hasFlash()) { + console.error("Flash Player is not installed."); + return; + } + + WebSocket = function(url, protocol, proxyHost, proxyPort, headers) { + var self = this; + self.readyState = WebSocket.CONNECTING; + self.bufferedAmount = 0; + WebSocket.__addTask(function() { + self.__flash = + WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null); + + self.__flash.addEventListener("open", function(fe) { + try { + if (self.onopen) self.onopen(); + } catch (e) { + console.error(e.toString()); + } + }); + + self.__flash.addEventListener("close", function(fe) { + try { + if (self.onclose) self.onclose(); + } catch (e) { + console.error(e.toString()); + } + }); + + self.__flash.addEventListener("message", function(fe) { + var data = decodeURIComponent(fe.getData()); + try { + if (self.onmessage) { + var e; + if (window.MessageEvent) { + e = document.createEvent("MessageEvent"); + e.initMessageEvent("message", false, false, data, null, null, window); + } else { // IE + e = {data: data}; + } + self.onmessage(e); + } + } catch (e) { + console.error(e.toString()); + } + }); + + self.__flash.addEventListener("stateChange", function(fe) { + try { + self.readyState = fe.getReadyState(); + self.bufferedAmount = fe.getBufferedAmount(); + } catch (e) { + console.error(e.toString()); + } + }); + + //console.log("[WebSocket] Flash object is ready"); + }); + } + + WebSocket.prototype.send = function(data) { + if (!this.__flash || this.readyState == WebSocket.CONNECTING) { + throw "INVALID_STATE_ERR: Web Socket connection has not been established"; + } + var result = this.__flash.send(data); + if (result < 0) { // success + return true; + } else { + this.bufferedAmount = result; + return false; + } + }; + + WebSocket.prototype.close = function() { + if (!this.__flash) return; + if (this.readyState != WebSocket.OPEN) return; + this.__flash.close(); + // Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events + // which causes weird error: + // > You are trying to call recursively into the Flash Player which is not allowed. + this.readyState = WebSocket.CLOSED; + if (this.onclose) this.onclose(); + }; + + /** + * Implementation of {@link DOM 2 EventTarget Interface} + * + * @param {string} type + * @param {function} listener + * @param {boolean} useCapture !NB Not implemented yet + * @return void + */ + WebSocket.prototype.addEventListener = function(type, listener, useCapture) { + if (!('__events' in this)) { + this.__events = {}; + } + if (!(type in this.__events)) { + this.__events[type] = []; + if ('function' == typeof this['on' + type]) { + this.__events[type].defaultHandler = this['on' + type]; + this['on' + type] = WebSocket_FireEvent(this, type); + } + } + this.__events[type].push(listener); + }; + + /** + * Implementation of {@link DOM 2 EventTarget Interface} + * + * @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 DOM 2 EventTarget Interface} + * + * @param {WebSocketEvent} event + * @return void + */ + WebSocket.prototype.dispatchEvent = function(event) { + if (!('__events' in this)) throw 'UNSPECIFIED_EVENT_TYPE_ERR'; + if (!(event.type in this.__events)) throw 'UNSPECIFIED_EVENT_TYPE_ERR'; + + for (var i = 0, l = this.__events[event.type].length; i < l; ++ i) { + this.__events[event.type][i](event); + if (event.cancelBubble) break; + } + + if (false !== event.returnValue && + 'function' == typeof this.__events[event.type].defaultHandler) + { + this.__events[event.type].defaultHandler(event); + } + }; + + /** + * + * @param {object} object + * @param {string} type + */ + function WebSocket_FireEvent(object, type) { + return function(data) { + var event = new WebSocketEvent(); + event.initEvent(type, true, true); + event.target = event.currentTarget = object; + for (var key in data) { + event[key] = data[key]; + } + object.dispatchEvent(event, arguments); + }; + } + + /** + * Basic implementation of {@link DOM 2 EventInterface} + * + * @class + * @constructor + */ + function WebSocketEvent(){} + + /** + * + * @type boolean + */ + WebSocketEvent.prototype.cancelable = true; + + /** + * + * @type boolean + */ + WebSocketEvent.prototype.cancelBubble = false; + + /** + * + * @return void + */ + WebSocketEvent.prototype.preventDefault = function() { + if (this.cancelable) { + this.returnValue = false; + } + }; + + /** + * + * @return void + */ + WebSocketEvent.prototype.stopPropagation = function() { + this.cancelBubble = true; + }; + + /** + * + * @param {string} eventTypeArg + * @param {boolean} canBubbleArg + * @param {boolean} cancelableArg + * @return void + */ + WebSocketEvent.prototype.initEvent = function(eventTypeArg, canBubbleArg, cancelableArg) { + this.type = eventTypeArg; + this.cancelable = cancelableArg; + this.timeStamp = new Date(); + }; + + + WebSocket.CONNECTING = 0; + WebSocket.OPEN = 1; + WebSocket.CLOSED = 2; + + WebSocket.__tasks = []; + + WebSocket.__initialize = function() { + if (!WebSocket.__swfLocation) { + console.error("[WebSocket] set WebSocket.__swfLocation to location of WebSocketMain.swf"); + return; + } + var container = document.createElement("div"); + container.id = "webSocketContainer"; + // Puts the Flash out of the window. Note that we cannot use display: none or visibility: hidden + // here because it prevents Flash from loading at least in IE. + container.style.position = "absolute"; + container.style.left = "-100px"; + container.style.top = "-100px"; + var holder = document.createElement("div"); + holder.id = "webSocketFlash"; + container.appendChild(holder); + document.body.appendChild(container); + swfobject.embedSWF( + WebSocket.__swfLocation, "webSocketFlash", "8", "8", "9.0.0", + null, {bridgeName: "webSocket"}, null, null, + function(e) { + if (!e.success) console.error("[WebSocket] swfobject.embedSWF failed"); + } + ); + FABridge.addInitializationCallback("webSocket", function() { + try { + //console.log("[WebSocket] FABridge initializad"); + WebSocket.__flash = FABridge.webSocket.root(); + WebSocket.__flash.setCallerUrl(location.href); + for (var i = 0; i < WebSocket.__tasks.length; ++i) { + WebSocket.__tasks[i](); + } + WebSocket.__tasks = []; + } catch (e) { + console.error("[WebSocket] " + e.toString()); + } + }); + }; + + WebSocket.__addTask = function(task) { + if (WebSocket.__flash) { + task(); + } else { + WebSocket.__tasks.push(task); + } + } + + // called from Flash + function webSocketLog(message) { + console.log(decodeURIComponent(message)); + } + + // called from Flash + function webSocketError(message) { + console.error(decodeURIComponent(message)); + } + + if (window.addEventListener) { + window.addEventListener("load", WebSocket.__initialize, false); + } else { + window.attachEvent("onload", WebSocket.__initialize); + } + +})();