Import web-socket-js: a0fb3933ce5c824bcb882f5a1cf87e46de773ea8

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

From: http://github.com/gimite/web-socket-js
This commit is contained in:
Joel Martin
2010-04-17 17:14:33 -05:00
parent 5d2c386400
commit d920595453
12 changed files with 2613 additions and 0 deletions

View File

@@ -0,0 +1,254 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// Lincense: New BSD Lincense
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31
package {
import flash.display.*;
import flash.events.*;
import flash.external.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
import mx.core.*;
import mx.controls.*;
import mx.events.*;
import mx.utils.*;
import com.adobe.net.proxies.RFC2817Socket;
[Event(name="message", type="WebSocketMessageEvent")]
[Event(name="open", type="flash.events.Event")]
[Event(name="close", type="flash.events.Event")]
[Event(name="stateChange", type="WebSocketStateEvent")]
public class WebSocket extends EventDispatcher {
private static var CONNECTING:int = 0;
private static var OPEN:int = 1;
private static var CLOSED:int = 2;
private var socket:RFC2817Socket;
private var main:WebSocketMain;
private var scheme:String;
private var host:String;
private var port:uint;
private var path:String;
private var origin:String;
private var protocol:String;
private var buffer:ByteArray = new ByteArray();
private var headerState:int = 0;
private var readyState:int = CONNECTING;
private var bufferedAmount:int = 0;
private var headers:String;
public function WebSocket(
main:WebSocketMain, url:String, protocol:String,
proxyHost:String = null, proxyPort:int = 0,
headers:String = null) {
this.main = main;
var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?$/);
if (!m) main.fatal("invalid url: " + url);
this.scheme = m[1];
this.host = m[2];
this.port = parseInt(m[4] || "80");
this.path = m[5] || "/";
this.origin = main.getOrigin();
this.protocol = protocol;
// if present and not the empty string, headers MUST end with \r\n
// headers should be zero or more complete lines, for example
// "Header1: xxx\r\nHeader2: yyyy\r\n"
this.headers = headers;
socket = new RFC2817Socket();
// if no proxy information is supplied, it acts like a normal Socket
// @see RFC2817Socket::connect
if (proxyHost != null && proxyPort != 0){
socket.setProxyInfo(proxyHost, proxyPort);
}
socket.addEventListener(Event.CLOSE, onSocketClose);
socket.addEventListener(Event.CONNECT, onSocketConnect);
socket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
socket.connect(host, port);
}
public function send(data:String):int {
if (readyState == OPEN) {
socket.writeByte(0x00);
socket.writeUTFBytes(data);
socket.writeByte(0xff);
socket.flush();
main.log("sent: " + data);
return -1;
} else if (readyState == CLOSED) {
var bytes:ByteArray = new ByteArray();
bytes.writeUTFBytes(data);
bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff
// We use return value to let caller know bufferedAmount because we cannot fire
// stateChange event here which causes weird error:
// > You are trying to call recursively into the Flash Player which is not allowed.
return bufferedAmount;
} else {
main.fatal("invalid state");
return 0;
}
}
public function close():void {
main.log("close");
try {
socket.close();
} catch (ex:Error) { }
readyState = CLOSED;
// We don't fire any events here because it causes weird error:
// > You are trying to call recursively into the Flash Player which is not allowed.
// We do something equivalent in JavaScript WebSocket#close instead.
}
public function getReadyState():int {
return readyState;
}
public function getBufferedAmount():int {
return bufferedAmount;
}
private function onSocketConnect(event:Event):void {
main.log("connected");
var hostValue:String = host + (port == 80 ? "" : ":" + port);
var cookie:String = "";
if (main.getCallerHost() == host) {
cookie = ExternalInterface.call("function(){return document.cookie}");
}
var opt:String = "";
if (protocol) opt += "WebSocket-Protocol: " + protocol + "\r\n";
// if caller passes additional headers they must end with "\r\n"
if (headers) opt += headers;
var req:String = StringUtil.substitute(
"GET {0} HTTP/1.1\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Host: {1}\r\n" +
"Origin: {2}\r\n" +
"Cookie: {4}\r\n" +
"{3}" +
"\r\n",
path, hostValue, origin, opt, cookie);
main.log("request header:\n" + req);
socket.writeUTFBytes(req);
socket.flush();
}
private function onSocketClose(event:Event):void {
main.log("closed");
readyState = CLOSED;
notifyStateChange();
dispatchEvent(new Event("close"));
}
private function onSocketIoError(event:IOErrorEvent):void {
close();
main.fatal("failed to connect Web Socket server (IoError)");
}
private function onSocketSecurityError(event:SecurityErrorEvent):void {
close();
main.fatal(
"failed to connect Web Socket server (SecurityError)\n" +
"make sure the server is running and Flash socket policy file is correctly placed");
}
private function onSocketData(event:ProgressEvent):void {
var pos:int = buffer.length;
socket.readBytes(buffer, pos);
for (; pos < buffer.length; ++pos) {
if (headerState != 4) {
// try to find "\r\n\r\n"
if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
++headerState;
} else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
++headerState;
} else {
headerState = 0;
}
if (headerState == 4) {
var headerStr:String = buffer.readUTFBytes(pos + 1);
main.log("response header:\n" + headerStr);
validateHeader(headerStr);
makeBufferCompact();
pos = -1;
readyState = OPEN;
notifyStateChange();
dispatchEvent(new Event("open"));
}
} else {
if (buffer[pos] == 0xff) {
if (buffer.readByte() != 0x00) {
close();
main.fatal("data must start with \\x00");
}
var data:String = buffer.readUTFBytes(pos - 1);
main.log("received: " + data);
dispatchEvent(new WebSocketMessageEvent("message", encodeURIComponent(data)));
buffer.readByte();
makeBufferCompact();
pos = -1;
}
}
}
}
private function validateHeader(headerStr:String):void {
var lines:Array = headerStr.split(/\r\n/);
if (!lines[0].match(/^HTTP\/1.1 101 /)) {
close();
main.fatal("bad response: " + lines[0]);
}
var header:Object = {};
for (var i:int = 1; i < lines.length; ++i) {
if (lines[i].length == 0) continue;
var m:Array = lines[i].match(/^(\S+): (.*)$/);
if (!m) {
close();
main.fatal("failed to parse response header line: " + lines[i]);
}
header[m[1]] = m[2];
}
if (header["Upgrade"] != "WebSocket") {
close();
main.fatal("invalid Upgrade: " + header["Upgrade"]);
}
if (header["Connection"] != "Upgrade") {
close();
main.fatal("invalid Connection: " + header["Connection"]);
}
var resOrigin:String = header["WebSocket-Origin"].toLowerCase();
if (resOrigin != origin) {
close();
main.fatal("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
}
if (protocol && header["WebSocket-Protocol"] != protocol) {
close();
main.fatal("protocol doesn't match: '" +
header["WebSocket-Protocol"] + "' != '" + protocol + "'");
}
}
private function makeBufferCompact():void {
if (buffer.position == 0) return;
var nextBuffer:ByteArray = new ByteArray();
buffer.readBytes(nextBuffer);
buffer = nextBuffer;
}
private function notifyStateChange():void {
dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount));
}
}
}

View File

@@ -0,0 +1,83 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// Lincense: New BSD Lincense
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31
package {
import flash.display.*;
import flash.events.*;
import flash.external.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
import mx.core.*;
import mx.controls.*;
import mx.events.*;
import mx.utils.*;
import bridge.FABridge;
public class WebSocketMain extends Sprite {
private var policyLoaded:Boolean = false;
private var callerUrl:String;
public function WebSocketMain() {
// This is to avoid "You are trying to call recursively into the Flash Player ..."
// error which (I heard) happens when you pass bunch of messages.
// This workaround was written here:
// http://www.themorphicgroup.com/blog/2009/02/14/fabridge-error-you-are-trying-to-call-recursively-into-the-flash-player-which-is-not-allowed/
FABridge.EventsToCallLater["flash.events::Event"] = "true";
FABridge.EventsToCallLater["WebSocketMessageEvent"] = "true";
FABridge.EventsToCallLater["WebSocketStateEvent"] = "true";
var fab:FABridge = new FABridge();
fab.rootObject = this;
//log("Flash initialized");
}
public function setCallerUrl(url:String):void {
callerUrl = url;
}
public function create(
url:String, protocol:String,
proxyHost:String = null, proxyPort:int = 0,
headers:String = null):WebSocket {
loadPolicyFile(null);
return new WebSocket(this, url, protocol, proxyHost, proxyPort, headers);
}
public function getOrigin():String {
return (URLUtil.getProtocol(this.callerUrl) + "://" +
URLUtil.getServerNameWithPort(this.callerUrl)).toLowerCase();
}
public function getCallerHost():String {
return URLUtil.getServerName(this.callerUrl);
}
public function loadPolicyFile(url:String):void {
if (policyLoaded && !url) return;
if (!url) {
url = "xmlsocket://" + URLUtil.getServerName(this.callerUrl) + ":843";
}
log("policy file: " + url);
Security.loadPolicyFile(url);
policyLoaded = true;
}
public function log(message:String):void {
ExternalInterface.call("webSocketLog", encodeURIComponent("[WebSocket] " + message));
}
public function fatal(message:String):void {
ExternalInterface.call("webSocketError", encodeURIComponent("[WebSocket] " + message));
throw message;
}
}
}

View File

@@ -0,0 +1,30 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// Lincense: New BSD Lincense
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31
package {
import flash.display.*;
import flash.events.*;
import flash.external.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
import mx.core.*;
import mx.controls.*;
import mx.events.*;
import mx.utils.*;
public class WebSocketMessageEvent extends Event {
public var data:String;
public function WebSocketMessageEvent(type:String, data:String) {
super(type);
this.data = data;
}
}
}

View File

@@ -0,0 +1,32 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// Lincense: New BSD Lincense
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31
package {
import flash.display.*;
import flash.events.*;
import flash.external.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
import mx.core.*;
import mx.controls.*;
import mx.events.*;
import mx.utils.*;
public class WebSocketStateEvent extends Event {
public var readyState:int;
public var bufferedAmount:int;
public function WebSocketStateEvent(type:String, readyState:int, bufferedAmount:int) {
super(type);
this.readyState = readyState;
this.bufferedAmount = bufferedAmount;
}
}
}

View File

@@ -0,0 +1,943 @@
/*
Copyright <20> 2006 Adobe Systems Incorporated
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The Bridge class, responsible for navigating JS instances
*/
package bridge
{
/*
* imports
*/
import flash.external.ExternalInterface;
import flash.utils.Timer;
import flash.events.*;
import flash.display.DisplayObject;
import flash.system.ApplicationDomain;
import flash.utils.Dictionary;
import flash.utils.setTimeout;
import mx.collections.errors.ItemPendingError;
import mx.core.IMXMLObject;
import flash.utils.getQualifiedClassName;
import flash.utils.describeType;
import flash.events.TimerEvent;
/**
* The FABridge class, responsible for proxying AS objects into javascript
*/
public class FABridge extends EventDispatcher implements IMXMLObject
{
//holds a list of stuff to call later, to break the recurrence of the js <> as calls
//you must use the full class name, as returned by the getQualifiedClassName() function
public static const MethodsToCallLater:Object = new Object();
MethodsToCallLater["mx.collections::ArrayCollection"]="refresh,removeItemAt";
public static const EventsToCallLater:Object = new Object();
EventsToCallLater["mx.data.events::UnresolvedConflictsEvent"]="true";
EventsToCallLater["mx.events::PropertyChangeEvent"]="true";
public static const INITIALIZED:String = "bridgeInitialized";
// constructor
public function FABridge()
{
super();
initializeCallbacks();
}
// private vars
/**
* stores a cache of descriptions of AS types suitable for sending to JS
*/
private var localTypeMap:Dictionary = new Dictionary();
/**
* stores an id-referenced dictionary of objects exported to JS
*/
private var localInstanceMap:Dictionary = new Dictionary();
/**
* stores an id-referenced dictionary of functions exported to JS
*/
private var localFunctionMap:Dictionary = new Dictionary();
/**
* stores an id-referenced dictionary of proxy functions imported from JS
*/
private var remoteFunctionCache:Dictionary = new Dictionary();
/**
* stores a list of custom serialization functions
*/
private var customSerializersMap:Dictionary = new Dictionary();
/**
* stores a map of object ID's and their reference count
*/
private var refMap:Dictionary = new Dictionary();
/**
* a local counter for generating unique IDs
*/
private var nextID:Number = 0;
private var lastRef:int;
/* values that can't be serialized natively across the bridge are packed and identified by type.
These constants represent different serialization types */
public static const TYPE_ASINSTANCE:uint = 1;
public static const TYPE_ASFUNCTION:uint = 2;
public static const TYPE_JSFUNCTION:uint = 3;
public static const TYPE_ANONYMOUS:uint = 4;
private var _initChecked:Boolean = false;
// properties
//getters and setters for the main component in the swf - the root
public function get rootObject():DisplayObject {return _rootObject;}
public function set rootObject(value:DisplayObject):void
{
_rootObject = value;
checkInitialized();
}
/**
* the bridge name
*/
public var bridgeName:String;
private var _registerComplete:Boolean = false;
/**
* increment the reference count for an object being passed over the bridge
*/
public function incRef(objId:int):void
{
if(refMap[objId] == null) {
//the object is being created; we now add it to the map and set its refCount = 1
refMap[objId] = 1;
} else {
refMap[objId] = refMap[objId] +1;
}
}
/**
* when an object has been completely passed to JS its reference count is decreased with 1
*/
public function releaseRef(objId:int):void
{
if(refMap[objId] != null)
{
var newRefVal:int = refMap[objId] - 1;
// if the object exists in the referenceMap and its count equals or has dropped under 0 we clean it up
if(refMap[objId] != null && newRefVal <= 0)
{
delete refMap[objId];
delete localInstanceMap[objId];
}
else
{
refMap[objId] = newRefVal;
}
}
}
/**
* attaches the callbacks to external interface
*/
public function initializeCallbacks():void
{
if (ExternalInterface.available == false)
{
return;
}
ExternalInterface.addCallback("getRoot", js_getRoot);
ExternalInterface.addCallback("getPropFromAS", js_getPropFromAS);
ExternalInterface.addCallback("setPropInAS", js_setPropertyInAS);
ExternalInterface.addCallback("invokeASMethod", js_invokeMethod);
ExternalInterface.addCallback("invokeASFunction", js_invokeFunction);
ExternalInterface.addCallback("releaseASObjects", js_releaseASObjects);
ExternalInterface.addCallback("create", js_create);
ExternalInterface.addCallback("releaseNamedASObject",js_releaseNamedASObject);
ExternalInterface.addCallback("incRef", incRef);
ExternalInterface.addCallback("releaseRef", releaseRef);
}
private var _rootObject:DisplayObject;
private var _document:DisplayObject;
/**
* called to check whether the bridge has been initialized for the specified document/id pairs
*/
public function initialized(document:Object, id:String):void
{
_document = (document as DisplayObject);
if (_document != null)
{
checkInitialized();
}
}
private function get baseObject():DisplayObject
{
return (rootObject == null)? _document:rootObject;
}
private function checkInitialized():void
{
if(_initChecked== true)
{
return;
}
_initChecked = true;
// oops! timing error. Player team is working on it.
var t:Timer = new Timer(200,1);
t.addEventListener(TimerEvent.TIMER,auxCheckInitialized);
t.start();
}
/**
* auxiliary initialization check that is called after the timing has occurred
*/
private function auxCheckInitialized(e:Event):void
{
var bCanGetParams:Boolean = true;
try
{
var params:Object = baseObject.root.loaderInfo.parameters;
}
catch (e:Error)
{
bCanGetParams = false;
}
if (bCanGetParams == false)
{
var t:Timer = new Timer(100);
var timerFunc:Function = function(e:TimerEvent):void
{
if(baseObject.root != null)
{
try
{
bCanGetParams = true;
var params:Object = baseObject.root.loaderInfo.parameters;
}
catch (err:Error)
{
bCanGetParams = false;
}
if (bCanGetParams)
{
t.removeEventListener(TimerEvent.TIMER, timerFunc);
t.stop();
dispatchInit();
}
}
}
t.addEventListener(TimerEvent.TIMER, timerFunc);
t.start();
}
else
{
dispatchInit();
}
}
/**
* call into JS to annunce that the bridge is ready to be used
*/
private function dispatchInit(e:Event = null):void
{
if(_registerComplete == true)
{
return;
}
if (ExternalInterface.available == false)
{
return;
}
if (bridgeName == null)
{
bridgeName = baseObject.root.loaderInfo.parameters["bridgeName"];
if(bridgeName == null)
{
bridgeName = "flash";
}
}
_registerComplete = ExternalInterface.call("FABridge__bridgeInitialized", [bridgeName]);
dispatchEvent(new Event(FABridge.INITIALIZED));
}
// serialization/deserialization
/** serializes a value for transfer across the bridge. primitive types are left as is. Arrays are left as arrays, but individual
* values in the array are serialized according to their type. Functions and class instances are inserted into a hash table and sent
* across as keys into the table.
*
* For class instances, if the instance has been sent before, only its id is passed. If This is the first time the instance has been sent,
* a ref descriptor is sent associating the id with a type string. If this is the first time any instance of that type has been sent
* across, a descriptor indicating methods, properties, and variables of the type is also sent across
*/
public function serialize(value:*, keep_refs:Boolean=false):*
{
var result:* = {};
result.newTypes = [];
result.newRefs = {};
if (value is Number || value is Boolean || value is String || value == null || value == undefined || value is int || value is uint)
{
result = value;
}
else if (value is Array)
{
result = [];
for(var i:int = 0; i < value.length; i++)
{
result[i] = serialize(value[i], keep_refs);
}
}
else if (value is Function)
{
// serialize a class
result.type = TYPE_ASFUNCTION;
result.value = getFunctionID(value, true);
}
else if (getQualifiedClassName(value) == "Object")
{
result.type = TYPE_ANONYMOUS;
result.value = value;
}
else
{
// serialize a class
result.type = TYPE_ASINSTANCE;
// make sure the type info is available
var className:String = getQualifiedClassName(value);
var serializer:Function = customSerializersMap[className];
// try looking up the serializer under an alternate name
if (serializer == null)
{
if (className.indexOf('$') > 0)
{
var split:int = className.lastIndexOf(':');
if (split > 0)
{
var alternate:String = className.substring(split+1);
serializer = customSerializersMap[alternate];
}
}
}
if (serializer != null)
{
return serializer.apply(null, [value, keep_refs]);
}
else
{
if (retrieveCachedTypeDescription(className, false) == null)
{
try
{
result.newTypes.push(retrieveCachedTypeDescription(className, true));
}
catch(err:Error)
{
var interfaceInfo:XMLList = describeType(value).implementsInterface;
for each (var interf:XML in interfaceInfo)
{
className = interf.@type.toString();
if (retrieveCachedTypeDescription(className, false) == null){
result.newTypes.push(retrieveCachedTypeDescription(className, true));
} //end if push new data type
} //end for going through interfaces
var baseClass:String = describeType(value).@base.toString();
if (retrieveCachedTypeDescription(baseClass, false) == null){
result.newTypes.push(retrieveCachedTypeDescription(baseClass, true));
} //end if push new data type
}
}
// make sure the reference is known
var objRef:Number = getRef(value, false);
var should_keep_ref:Boolean = false;
if (isNaN(objRef))
{
//create the reference if necessary
objRef = getRef(value, true);
should_keep_ref = true;
}
result.newRefs[objRef] = className;
//trace("serializing new reference: " + className + " with value" + value);
//the result is a getProperty / invokeMethod call. How can we know how much you will need the object ?
if (keep_refs && should_keep_ref) {
incRef(objRef);
}
result.value = objRef;
}
}
return result;
}
/**
* deserializes a value passed in from javascript. See serialize for details on how values are packed and
* unpacked for transfer across the bridge.
*/
public function deserialize(valuePackage:*):*
{
var result:*;
if (valuePackage is Number || valuePackage is Boolean || valuePackage is String || valuePackage === null || valuePackage === undefined || valuePackage is int || valuePackage is uint)
{
result = valuePackage;
}
else if(valuePackage is Array)
{
result = [];
for (var i:int = 0; i < valuePackage.length; i++)
{
result[i] = deserialize(valuePackage[i]);
}
}
else if (valuePackage.type == FABridge.TYPE_JSFUNCTION)
{
result = getRemoteFunctionProxy(valuePackage.value, true);
}
else if (valuePackage.type == FABridge.TYPE_ASFUNCTION)
{
throw new Error("as functions can't be passed back to as yet");
}
else if (valuePackage.type == FABridge.TYPE_ASINSTANCE)
{
result = resolveRef(valuePackage.value);
}
else if (valuePackage.type == FABridge.TYPE_ANONYMOUS)
{
result = valuePackage.value;
}
return result;
}
public function addCustomSerialization(className:String, serializationFunction:Function):void
{
customSerializersMap[className] = serializationFunction;
}
// type management
/**
* retrieves a type description for the type indicated by className, building one and caching it if necessary
*/
public function retrieveCachedTypeDescription(className:String, createifNecessary:Boolean):Object
{
if(localTypeMap[className] == null && createifNecessary == true)
{
localTypeMap[className] = buildTypeDescription(className);
}
return localTypeMap[className];
}
public function addCachedTypeDescription(className:String, desc:Object):Object
{
if (localTypeMap[className] == null)
{
localTypeMap[className] = desc;
}
return localTypeMap[className];
}
/**
* builds a type description for the type indiciated by className
*/
public function buildTypeDescription(className:String):Object
{
var desc:Object = {};
className = className.replace(/::/,".");
var objClass:Class = Class(ApplicationDomain.currentDomain.getDefinition(className));
var xData:XML = describeType(objClass);
desc.name = xData.@name.toString();
var methods:Array = [];
var xMethods:XMLList = xData.factory.method;
for (var i:int = 0; i < xMethods.length(); i++)
{
methods.push(xMethods[i].@name.toString());
}
desc.methods = methods;
var accessors:Array = [];
var xAcc:XMLList = xData.factory.accessor;
for (i = 0; i < xAcc.length(); i++)
{
accessors.push(xAcc[i].@name.toString());
}
xAcc = xData.factory.variable;
for (i = 0; i < xAcc.length(); i++)
{
accessors.push(xAcc[i].@name.toString());
}
desc.accessors = accessors;
return desc;
}
// instance mgmt
/**
* resolves an instance id passed from JS to an instance previously cached for representing in JS
*/
private function resolveRef(objRef:Number):Object
{
try
{
return (objRef == -1)? baseObject : localInstanceMap[objRef];
}
catch(e:Error)
{
return serialize("__FLASHERROR__"+"||"+e.message);
}
return (objRef == -1)? baseObject : localInstanceMap[objRef];
}
/**
* returns an id associated with the object provided for passing across the bridge to JS
*/
public function getRef(obj:Object, createIfNecessary:Boolean):Number
{
try
{
var ref:Number;
if (createIfNecessary)
{
var newRef:Number = nextID++;
localInstanceMap[newRef] = obj;
ref = newRef;
}
else
{
for (var key:* in localInstanceMap)
{
if (localInstanceMap[key] === obj)
{
ref = key;
break;
}
}
}
}
catch(e:Error)
{
return serialize("__FLASHERROR__"+"||"+e.message)
}
return ref;
}
// function management
/**
* resolves a function ID passed from JS to a local function previously cached for representation in JS
*/
private function resolveFunctionID(funcID:Number):Function
{
return localFunctionMap[funcID];
}
/**
* associates a unique ID with a local function suitable for passing across the bridge to proxy in Javascript
*/
public function getFunctionID(f:Function, createIfNecessary:Boolean):Number
{
var ref:Number;
if (createIfNecessary)
{
var newID:Number = nextID++;
localFunctionMap[newID] = f;
ref = newID;
}
else
{
for (var key:* in localFunctionMap)
{
if (localFunctionMap[key] === f) {
ref = key;
}
break;
}
}
return ref;
}
/**
* returns a proxy function that represents a function defined in javascript. This function can be called syncrhonously, and will
* return any values returned by the JS function
*/
public function getRemoteFunctionProxy(functionID:Number, createIfNecessary:Boolean):Function
{
try
{
if (remoteFunctionCache[functionID] == null)
{
remoteFunctionCache[functionID] = function(...args):*
{
var externalArgs:Array = args.concat();
externalArgs.unshift(functionID);
var serializedArgs:* = serialize(externalArgs, true);
if(checkToThrowLater(serializedArgs[1]))
{
setTimeout(function a():* {
try {
var retVal:* = ExternalInterface.call("FABridge__invokeJSFunction", serializedArgs);
for(var i:int = 0; i<serializedArgs.length; i++)
{
if(typeof(serializedArgs[i]) == "object" && serializedArgs[i]!=null)
{
releaseRef(serializedArgs[i].value);
}
}
return retVal;
}
catch(e:Error)
{
return serialize("__FLASHERROR__"+"||"+e.message);
}
},1);
}
else
{
var retVal:* = ExternalInterface.call("FABridge__invokeJSFunction", serializedArgs);
for(var i:int = 0; i<serializedArgs.length; i++)
{
if(typeof(serializedArgs[i]) == "object" && serializedArgs[i]!=null)
{
releaseRef(serializedArgs[i].value);
}
}
return retVal;
}
}
}
}
catch(e:Error)
{
return serialize("__FLASHERROR__"+"||"+e.message);
}
return remoteFunctionCache[functionID];
}
/**
* function that checks if the object on which we are working demands that it should be called at a later time, breaking the call chain
* we check the actual object, as well as the bsae class and interfaces
*/
private function checkToThrowLater(obj:Object):Boolean
{
obj = resolveRef(obj.value);
var className:String = getQualifiedClassName(obj);
var classInfo:XML = describeType(obj);
if (FABridge.EventsToCallLater[className] != null) {
return true;
}
//check if this class doesn't inherit from one of the entries in the table
var inheritanceInfo:XMLList = describeType(obj).extendsClass;
for each (var inherit:XML in inheritanceInfo)
{
className = inherit.@type.toString();
if (FABridge.EventsToCallLater[className] != null) {
return true;
}
} //end for going through inheritance tree
//if we're still here, check the interfaces as well
var interfaceInfo:XMLList = describeType(obj).implementsInterface;
for each (var interf:XML in interfaceInfo)
{
className = interf.@type.toString();
if (FABridge.EventsToCallLater[className] != null) {
return true;
}
} //end for going through inheritance tree
//if nothing was found, return false, so the function gets executed
return false;
}
// callbacks exposed to JS
/**
* called to fetch a named property off the instanced associated with objID
*/
public function js_getPropFromAS(objID:Number, propName:String):*
{
incRef(objID);
try
{
var obj:Object = resolveRef(objID);
var ret:* = serialize(obj[propName], true);
releaseRef(objID);
return ret;
}
catch (e:ItemPendingError)
{
releaseRef(objID);
//ItemPendingError
//return serialize("an error occcured with" + obj[propName]);
}
catch(e:Error)
{
releaseRef(objID);
return serialize("__FLASHERROR__" + "||" + e.message);
}
}
/**
* called to set a named property on the instance associated with objID
*/
private function js_setPropertyInAS(objID:Number, propRef:String, value:*):*
{
incRef(objID);
try {
var obj:Object = resolveRef(objID);
obj[propRef] = deserialize(value);
releaseRef(objID);
}
catch(e:Error)
{
releaseRef(objID);
return serialize("__FLASHERROR__" + "||" + e.message);
}
}
/**
* accessor for retrieveing a proxy to the root object from JS
*/
private function js_getRoot():*
{
try
{
//always get the root; this is the same as the get property, only it is the root object
var objRef:Number = getRef(baseObject, false);
if (isNaN(objRef))
{
//create the reference if necessary
objRef = getRef(baseObject, true);
incRef(objRef);
}
return serialize(baseObject);
}
catch(e:Error)
{
return serialize("__FLASHERROR__"+"||"+e.message);
}
}
/**
* called to invoke a function or closure associated with funcID
*/
private function js_invokeFunction(funcID:Number, args:Object):*
{
var result:*;
try
{
var func:Function = resolveFunctionID(funcID);
if(func != null)
result = func.apply(null, deserialize(args));
return serialize(result, true);
}
catch(e:Error)
{
return serialize("__FLASHERROR__"+"||"+e.message);
}
}
/**
* called to invoke a named method on the object associated with objID
*/
private function js_invokeMethod(objID:Number, methodName:String, args:Object):*
{
incRef(objID);
try
{
var obj:Object = resolveRef(objID);
var result:*;
//check if the method is callable right now, or later
var callLater:Boolean = checkToExecuteLater(obj, methodName);
if (callLater) {
var t:Timer = new Timer(200, 1);
t.addEventListener(TimerEvent.TIMER, function():void {
var ret_inner:* = serialize(obj[methodName].apply(null, deserialize(args)), true);
releaseRef(objID);
});
t.start();
} else {
var ret:* = serialize(obj[methodName].apply(null, deserialize(args)), true);
releaseRef(objID);
return ret;
}
}
catch (e:ItemPendingError)
{
releaseRef(objID);
// ignore ItemPendingError
}
catch(e:Error)
{
releaseRef(objID);
return serialize("__FLASHERROR__" + "||" + e.message);
}
}
/**
* method that performs a check on the specified object and method to see if their execution should be delayed or not
* it checks the object, its base class and implemented interfaces
*/
private function checkToExecuteLater(obj:Object, methodName:String):Boolean
{
var methods:String;
var className:String = getQualifiedClassName(obj);
var classInfo:XML = describeType(obj);
if (FABridge.MethodsToCallLater[className] != null) {
methods = FABridge.MethodsToCallLater[className];
//must call later
if(methods.match(methodName))
{
return true;
}
}
//check if this class doesn't inherit from one of the entries in the table
var inheritanceInfo:XMLList = describeType(obj).extendsClass;
for each (var inherit:XML in inheritanceInfo)
{
className = inherit.@type.toString();
if (FABridge.MethodsToCallLater[className] != null) {
methods = FABridge.MethodsToCallLater[className];
//must call later
if(methods.match(methodName))
{
return true;
}
}
} //end for going through inheritance tree
//if we're still here, check the interfaces as well
var interfaceInfo:XMLList = describeType(obj).implementsInterface;
for each (var interf:XML in interfaceInfo)
{
className = interf.@type.toString();
if (FABridge.MethodsToCallLater[className] != null) {
methods = FABridge.MethodsToCallLater[className];
//must call later
if(methods.match(methodName))
{
return true;
}
}
} //end for going through inheritance tree
//if nothing was found, return false, so the function gets executed
return false;
}
/**
* callback from JS to release all AS Objects from the local cache maps
*/
private function js_releaseASObjects():void
{
localTypeMap = new Dictionary();
localInstanceMap = new Dictionary();
localFunctionMap = new Dictionary();
}
/**
* callback from JS to release a specific object, identified by its ID
*/
private function js_releaseNamedASObject(objId:int):Boolean
{
var retVal:Boolean = false;
if (localInstanceMap[objId] != null)
{
delete refMap[objId];
delete localInstanceMap[objId];
retVal = true;
}
return retVal;
}
/**
* callback for js to create a new class instance.
*/
private function js_create(className:String):*
{
try
{
var c:Class = Class(ApplicationDomain.currentDomain.getDefinition(className));
var instance:Object = new c();
}
catch(e:Error)
{
return serialize("__FLASHERROR__" + "||" + e.message);
}
// make sure the reference is known
var objRef:Number = getRef(instance, true);
incRef(objRef);
return serialize(instance);
}
}
}

View File

@@ -0,0 +1,204 @@
/*
Adobe Systems Incorporated(r) Source Code License Agreement
Copyright(c) 2005 Adobe Systems Incorporated. All rights reserved.
Please read this Source Code License Agreement carefully before using
the source code.
Adobe Systems Incorporated grants to you a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable copyright license, to reproduce,
prepare derivative works of, publicly display, publicly perform, and
distribute this source code and such derivative works in source or
object code form without any attribution requirements.
The name "Adobe Systems Incorporated" must not be used to endorse or promote products
derived from the source code without prior written permission.
You agree to indemnify, hold harmless and defend Adobe Systems Incorporated from and
against any loss, damage, claims or lawsuits, including attorney's
fees that arise or result from your use or distribution of the source
code.
THIS SOURCE CODE IS PROVIDED "AS IS" AND "WITH ALL FAULTS", WITHOUT
ANY TECHNICAL SUPPORT OR ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ALSO, THERE IS NO WARRANTY OF
NON-INFRINGEMENT, TITLE OR QUIET ENJOYMENT. IN NO EVENT SHALL MACROMEDIA
OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOURCE CODE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.adobe.net.proxies
{
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.net.Socket;
/**
* This class allows TCP socket connections through HTTP proxies in accordance with
* RFC 2817:
*
* ftp://ftp.rfc-editor.org/in-notes/rfc2817.txt
*
* It can also be used to make direct connections to a destination, as well. If you
* pass the host and port into the constructor, no proxy will be used. You can also
* call connect, passing in the host and the port, and if you didn't set the proxy
* info, a direct connection will be made. A proxy is only used after you have called
* the setProxyInfo function.
*
* The connection to and negotiation with the proxy is completely hidden. All the
* same events are thrown whether you are using a proxy or not, and the data you
* receive from the target server will look exact as it would if you were connected
* to it directly rather than through a proxy.
*
* @author Christian Cantrell
*
**/
public class RFC2817Socket
extends Socket
{
private var proxyHost:String = null;
private var host:String = null;
private var proxyPort:int = 0;
private var port:int = 0;
private var deferredEventHandlers:Object = new Object();
private var buffer:String = new String();
/**
* Construct a new RFC2817Socket object. If you pass in the host and the port,
* no proxy will be used. If you want to use a proxy, instantiate with no
* arguments, call setProxyInfo, then call connect.
**/
public function RFC2817Socket(host:String = null, port:int = 0)
{
if (host != null && port != 0)
{
super(host, port);
}
}
/**
* Set the proxy host and port number. Your connection will only proxied if
* this function has been called.
**/
public function setProxyInfo(host:String, port:int):void
{
this.proxyHost = host;
this.proxyPort = port;
var deferredSocketDataHandler:Object = this.deferredEventHandlers[ProgressEvent.SOCKET_DATA];
var deferredConnectHandler:Object = this.deferredEventHandlers[Event.CONNECT];
if (deferredSocketDataHandler != null)
{
super.removeEventListener(ProgressEvent.SOCKET_DATA, deferredSocketDataHandler.listener, deferredSocketDataHandler.useCapture);
}
if (deferredConnectHandler != null)
{
super.removeEventListener(Event.CONNECT, deferredConnectHandler.listener, deferredConnectHandler.useCapture);
}
}
/**
* Connect to the specified host over the specified port. If you want your
* connection proxied, call the setProxyInfo function first.
**/
public override function connect(host:String, port:int):void
{
if (this.proxyHost == null)
{
this.redirectConnectEvent();
this.redirectSocketDataEvent();
super.connect(host, port);
}
else
{
this.host = host;
this.port = port;
super.addEventListener(Event.CONNECT, this.onConnect);
super.addEventListener(ProgressEvent.SOCKET_DATA, this.onSocketData);
super.connect(this.proxyHost, this.proxyPort);
}
}
private function onConnect(event:Event):void
{
this.writeUTFBytes("CONNECT "+this.host+":"+this.port+" HTTP/1.1\n\n");
this.flush();
this.redirectConnectEvent();
}
private function onSocketData(event:ProgressEvent):void
{
while (this.bytesAvailable != 0)
{
this.buffer += this.readUTFBytes(1);
if (this.buffer.search(/\r?\n\r?\n$/) != -1)
{
this.checkResponse(event);
break;
}
}
}
private function checkResponse(event:ProgressEvent):void
{
var responseCode:String = this.buffer.substr(this.buffer.indexOf(" ")+1, 3);
if (responseCode.search(/^2/) == -1)
{
var ioError:IOErrorEvent = new IOErrorEvent(IOErrorEvent.IO_ERROR);
ioError.text = "Error connecting to the proxy ["+this.proxyHost+"] on port ["+this.proxyPort+"]: " + this.buffer;
this.dispatchEvent(ioError);
}
else
{
this.redirectSocketDataEvent();
this.dispatchEvent(new Event(Event.CONNECT));
if (this.bytesAvailable > 0)
{
this.dispatchEvent(event);
}
}
this.buffer = null;
}
private function redirectConnectEvent():void
{
super.removeEventListener(Event.CONNECT, onConnect);
var deferredEventHandler:Object = this.deferredEventHandlers[Event.CONNECT];
if (deferredEventHandler != null)
{
super.addEventListener(Event.CONNECT, deferredEventHandler.listener, deferredEventHandler.useCapture, deferredEventHandler.priority, deferredEventHandler.useWeakReference);
}
}
private function redirectSocketDataEvent():void
{
super.removeEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
var deferredEventHandler:Object = this.deferredEventHandlers[ProgressEvent.SOCKET_DATA];
if (deferredEventHandler != null)
{
super.addEventListener(ProgressEvent.SOCKET_DATA, deferredEventHandler.listener, deferredEventHandler.useCapture, deferredEventHandler.priority, deferredEventHandler.useWeakReference);
}
}
public override function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int=0.0, useWeakReference:Boolean=false):void
{
if (type == Event.CONNECT || type == ProgressEvent.SOCKET_DATA)
{
this.deferredEventHandlers[type] = {listener:listener,useCapture:useCapture, priority:priority, useWeakReference:useWeakReference};
}
else
{
super.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
}
}
}