diff --git a/include/web-socket-js/FABridge.js b/include/web-socket-js/FABridge.js deleted file mode 100644 index df7e355c..00000000 --- a/include/web-socket-js/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/include/web-socket-js/WebSocketMain.swf b/include/web-socket-js/WebSocketMain.swf index 1e4df8d8..244c445b 100644 Binary files a/include/web-socket-js/WebSocketMain.swf and b/include/web-socket-js/WebSocketMain.swf differ diff --git a/include/web-socket-js/web_socket.js b/include/web-socket-js/web_socket.js index 48ec0fcb..ec2a8b72 100755 --- a/include/web-socket-js/web_socket.js +++ b/include/web-socket-js/web_socket.js @@ -12,8 +12,8 @@ console = {log: function(){ }, error: function(){ }}; } - if (!swfobject.hasFlashPlayerVersion("9.0.0")) { - console.error("Flash Player is not installed."); + if (!swfobject.hasFlashPlayerVersion("10.0.0")) { + console.error("Flash Player >= 10.0.0 is required."); return; } if (location.protocol == "file:") { @@ -23,33 +23,38 @@ "Open the page via Web server i.e. http://..."); } + /** + * This class represents a faux web socket. + * @param {string} url + * @param {string} protocol + * @param {string} proxyHost + * @param {int} proxyPort + * @param {string} headers + */ WebSocket = function(url, protocol, proxyHost, proxyPort, headers) { var self = this; + self.__id = WebSocket.__nextId++; + WebSocket.__instances[self.__id] = self; self.readyState = WebSocket.CONNECTING; self.bufferedAmount = 0; + self.__events = {}; // Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc. // Otherwise, when onopen fires immediately, onopen is called before it is set. setTimeout(function() { WebSocket.__addTask(function() { - self.__createFlash(url, protocol, proxyHost, proxyPort, headers); + WebSocket.__flash.create( + self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null); }); }, 0); }; - - WebSocket.prototype.__createFlash = function(url, protocol, proxyHost, proxyPort, headers) { - var self = this; - self.__flash = - WebSocket.__flash.create(url, protocol, proxyHost || null, proxyPort || 0, headers || null); - self.__flash.addEventListener("event", function(fe) { - // Uses setTimeout() to workaround the error: - // > You are trying to call recursively into the Flash Player which is not allowed. - setTimeout(function() { self.__handleEvents(); }, 0); - }); - //console.log("[WebSocket] Flash object is ready"); - }; + /** + * Send data to the web socket. + * @param {string} data The data to send to the socket. + * @return {boolean} True for success, false for failure. + */ WebSocket.prototype.send = function(data) { - if (!this.__flash || this.readyState == WebSocket.CONNECTING) { + if (this.readyState == WebSocket.CONNECTING) { throw "INVALID_STATE_ERR: Web Socket connection has not been established"; } // We use encodeURIComponent() here, because FABridge doesn't work if @@ -58,7 +63,9 @@ // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions // But it looks decodeURIComponent(encodeURIComponent(s)) doesn't // preserve all Unicode characters either e.g. "\uffff" in Firefox. - var result = this.__flash.send(encodeURIComponent(data)); + // Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require + // additional testing. + var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data)); if (result < 0) { // success return true; } else { @@ -67,21 +74,15 @@ } }; + /** + * Close this web socket gracefully. + */ WebSocket.prototype.close = function() { - var self = this; - if (!self.__flash) return; - if (self.readyState == WebSocket.CLOSED || self.readyState == WebSocket.CLOSING) return; - self.__flash.close(); - // Sets/calls them manually here because Flash WebSocketConnection.close cannot fire events - // which causes weird error: - // > You are trying to call recursively into the Flash Player which is not allowed. - self.readyState = WebSocket.CLOSED; - if (self.__timer) clearInterval(self.__timer); - if (self.onclose) { - // Make it asynchronous so that it looks more like an actual - // close event - setTimeout(self.onclose, 0); - } + if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) { + return; + } + this.readyState = WebSocket.CLOSING; + WebSocket.__flash.close(this.__id); }; /** @@ -89,19 +90,12 @@ * * @param {string} type * @param {function} listener - * @param {boolean} useCapture !NB Not implemented yet + * @param {boolean} useCapture * @return void */ WebSocket.prototype.addEventListener = function(type, listener, useCapture) { - if (!('__events' in this)) { - this.__events = {}; - } if (!(type in this.__events)) { this.__events[type] = []; - if ('function' == typeof this['on' + type]) { - this.__events[type].defaultHandler = this['on' + type]; - this['on' + type] = this.__createEventHandler(this, type); - } } this.__events[type].push(listener); }; @@ -111,17 +105,15 @@ * * @param {string} type * @param {function} listener - * @param {boolean} useCapture NB! Not implemented yet + * @param {boolean} useCapture * @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); + var events = this.__events[type]; + for (var i = events.length - 1; i >= 0; --i) { + if (events[i] === listener) { + events.splice(i, 1); break; } } @@ -130,164 +122,93 @@ /** * Implementation of {@link DOM 2 EventTarget Interface} * - * @param {WebSocketEvent} event + * @param {Event} 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); + var events = this.__events[event.type] || []; + for (var i = 0; i < events.length; ++i) { + events[i](event); } + var handler = this["on" + event.type]; + if (handler) handler(event); }; - WebSocket.prototype.__handleEvents = function() { - // Gets events using receiveEvents() instead of getting it from event object - // of Flash event. This is to make sure to keep message order. - // It seems sometimes Flash events don't arrive in the same order as they are sent. - var events = this.__flash.receiveEvents(); - for (var i = 0; i < events.length; i++) { - try { - var event = events[i]; - if ("readyState" in event) { - this.readyState = event.readyState; - } - if (event.type == "open") { - - if (this.__timer) clearInterval(this.__timer); - if (window.opera) { - // Workaround for weird behavior of Opera which sometimes drops events. - var that = this; - this.__timer = setInterval(function () { - that.__handleEvents(); - }, 500); - } - if (this.onopen) this.onopen(); - - } else if (event.type == "close") { - - if (this.__timer) clearInterval(this.__timer); - if (this.onclose) this.onclose(); - - } else if (event.type == "message") { - - if (this.onmessage) { - var data = decodeURIComponent(event.data); - var e; - if (window.MessageEvent && !window.opera) { - e = document.createEvent("MessageEvent"); - e.initMessageEvent("message", false, false, data, null, null, window, null); - } else { - // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. - e = {data: data}; - } - this.onmessage(e); - } - - } else if (event.type == "error") { - - if (this.__timer) clearInterval(this.__timer); - if (this.onerror) this.onerror(); - - } else { - throw "unknown event type: " + event.type; - } - } catch (e) { - console.error(e.toString()); - } + /** + * Handles an event from Flash. + * @param {Object} flashEvent + */ + WebSocket.prototype.__handleEvent = function(flashEvent) { + if ("readyState" in flashEvent) { + this.readyState = flashEvent.readyState; + } + + var jsEvent; + if (flashEvent.type == "open" || flashEvent.type == "error") { + jsEvent = this.__createSimpleEvent(flashEvent.type); + } else if (flashEvent.type == "close") { + // TODO implement jsEvent.wasClean + jsEvent = this.__createSimpleEvent("close"); + } else if (flashEvent.type == "message") { + var data = decodeURIComponent(flashEvent.message); + jsEvent = this.__createMessageEvent("message", data); + } else { + throw "unknown event type: " + flashEvent.type; + } + + this.dispatchEvent(jsEvent); + }; + + WebSocket.prototype.__createSimpleEvent = function(type) { + if (document.createEvent && window.Event) { + var event = document.createEvent("Event"); + event.initEvent(type, false, false); + return event; + } else { + return {type: type, bubbles: false, cancelable: false}; + } + }; + + WebSocket.prototype.__createMessageEvent = function(type, data) { + if (document.createEvent && window.MessageEvent && !window.opera) { + var event = document.createEvent("MessageEvent"); + event.initMessageEvent("message", false, false, data, null, null, window, null); + return event; + } else { + // IE and Opera, the latter one truncates the data parameter after any 0x00 bytes. + return {type: type, data: data, bubbles: false, cancelable: false}; } }; /** - * @param {object} object - * @param {string} type + * Define the WebSocket readyState enumeration. */ - WebSocket.prototype.__createEventHandler = function(object, type) { - return function(data) { - var event = new WebSocketEvent(); - event.initEvent(type, true, true); - event.target = event.currentTarget = object; - for (var key in data) { - event[key] = data[key]; - } - object.dispatchEvent(event, arguments); - }; - }; - - /** - * 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.CLOSING = 2; WebSocket.CLOSED = 3; + WebSocket.__flash = null; + WebSocket.__instances = {}; WebSocket.__tasks = []; - - WebSocket.loadFlashPolicyFile = function(url) { + WebSocket.__nextId = 0; + + /** + * Load a new flash security policy file. + * @param {string} url + */ + WebSocket.loadFlashPolicyFile = function(url){ WebSocket.__addTask(function() { WebSocket.__flash.loadManualPolicyFile(url); }); - } + }; + /** + * Loads WebSocketMain.swf and creates WebSocketMain object in Flash. + */ WebSocket.__initialize = function() { + if (WebSocket.__flash) return; + if (WebSocket.__swfLocation) { // For backword compatibility. window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation; @@ -318,29 +239,70 @@ // See this article for hasPriority: // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html swfobject.embedSWF( - WEB_SOCKET_SWF_LOCATION, "webSocketFlash", - "1" /* width */, "1" /* height */, "9.0.0" /* SWF version */, - null, {bridgeName: "webSocket"}, {hasPriority: true, allowScriptAccess: "always"}, null, + WEB_SOCKET_SWF_LOCATION, + "webSocketFlash", + "1" /* width */, + "1" /* height */, + "10.0.0" /* SWF version */, + null, + null, + {hasPriority: true, swliveconnect : true, allowScriptAccess: "always"}, + null, function(e) { - if (!e.success) console.error("[WebSocket] swfobject.embedSWF failed"); - } - ); - FABridge.addInitializationCallback("webSocket", function() { - try { - //console.log("[WebSocket] FABridge initializad"); - WebSocket.__flash = FABridge.webSocket.root(); - WebSocket.__flash.setCallerUrl(location.href); - WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); - for (var i = 0; i < WebSocket.__tasks.length; ++i) { - WebSocket.__tasks[i](); + if (!e.success) { + console.error("[WebSocket] swfobject.embedSWF failed"); } - WebSocket.__tasks = []; - } catch (e) { - console.error("[WebSocket] " + e.toString()); - } - }); + }); }; - + + /** + * Called by Flash to notify JS that it's fully loaded and ready + * for communication. + */ + WebSocket.__onFlashInitialized = function() { + // We need to set a timeout here to avoid round-trip calls + // to flash during the initialization process. + setTimeout(function() { + WebSocket.__flash = document.getElementById("webSocketFlash"); + WebSocket.__flash.setCallerUrl(location.href); + WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG); + for (var i = 0; i < WebSocket.__tasks.length; ++i) { + WebSocket.__tasks[i](); + } + WebSocket.__tasks = []; + }, 0); + }; + + /** + * Called by Flash to notify WebSockets events are fired. + */ + WebSocket.__onFlashEvent = function() { + setTimeout(function() { + try { + // Gets events using receiveEvents() instead of getting it from event object + // of Flash event. This is to make sure to keep message order. + // It seems sometimes Flash events don't arrive in the same order as they are sent. + var events = WebSocket.__flash.receiveEvents(); + for (var i = 0; i < events.length; ++i) { + WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]); + } + } catch (e) { + console.error(e); + } + }, 0); + return true; + }; + + // Called by Flash. + WebSocket.__log = function(message) { + console.log(decodeURIComponent(message)); + }; + + // Called by Flash. + WebSocket.__error = function(message) { + console.error(decodeURIComponent(message)); + }; + WebSocket.__addTask = function(task) { if (WebSocket.__flash) { task(); @@ -349,28 +311,30 @@ } }; + /** + * Test if the browser is running flash lite. + * @return {boolean} True if flash lite is running, false otherwise. + */ WebSocket.__isFlashLite = function() { - if (!window.navigator || !window.navigator.mimeTypes) return false; + if (!window.navigator || !window.navigator.mimeTypes) { + return false; + } var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"]; - if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) return false; + if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) { + return false; + } return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false; }; - - // called from Flash - window.webSocketLog = function(message) { - console.log(decodeURIComponent(message)); - }; - - // called from Flash - window.webSocketError = function(message) { - console.error(decodeURIComponent(message)); - }; - + if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) { if (window.addEventListener) { - window.addEventListener("load", WebSocket.__initialize, false); + window.addEventListener("load", function(){ + WebSocket.__initialize(); + }, false); } else { - window.attachEvent("onload", WebSocket.__initialize); + window.attachEvent("onload", function(){ + WebSocket.__initialize(); + }); } } diff --git a/include/websock.js b/include/websock.js index 09bdf9ba..dcf6453c 100644 --- a/include/websock.js +++ b/include/websock.js @@ -38,7 +38,6 @@ if (window.WebSocket) { WEB_SOCKET_SWF_LOCATION += "?" + Math.random(); } extra += start + "web-socket-js/swfobject.js" + end; - extra += start + "web-socket-js/FABridge.js" + end; extra += start + "web-socket-js/web_socket.js" + end; document.write(extra); }());