Subversion Repositories Applications.papyrus

Compare Revisions

Ignore whitespace Rev 2149 → Rev 2150

/trunk/api/js/dojo1.0/dojox/_cometd/README
New file
0,0 → 1,29
-------------------------------------------------------------------------------
Cometd (client)
-------------------------------------------------------------------------------
Version 0.4
Release date: May 29, 2007
-------------------------------------------------------------------------------
Project state: beta
-------------------------------------------------------------------------------
Project authors
Alex Russell (alex@dojotoolkit.org)
Greg Wilkins
-------------------------------------------------------------------------------
Project description
 
Low-latency data transfer from servers to clients. dojox.cometd implements a
Bayeux protocol client for use with most Bayeux servers. See cometd.com for
details on Cometd or on the Bayeux protocol.
-------------------------------------------------------------------------------
Dependencies:
 
Needs a cooperating Bayeux server
-------------------------------------------------------------------------------
Documentation
 
See http://cometd.com
-------------------------------------------------------------------------------
Installation instructions
 
Use this library with (preferably through) an existing Cometd server.
/trunk/api/js/dojo1.0/dojox/_cometd/cometd.js
New file
0,0 → 1,684
if(!dojo._hasResource["dojox._cometd.cometd"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox._cometd.cometd"] = true;
dojo.provide("dojox._cometd.cometd");
dojo.require("dojo.AdapterRegistry");
dojo.require("dojo.io.script");
 
// FIXME: need to add local topic support to advise about:
// successful handshake
// network failure of channel
// graceful disconnect
 
/*
* this file defines Comet protocol client. Actual message transport is
* deferred to one of several connection type implementations. The default is a
* long-polling implementation. A single global object named "dojox.cometd" is
* used to mediate for these connection types in order to provide a stable
* interface.
*/
 
dojox.cometd = new function(){
/* cometd states:
* DISCONNECTED: _initialized==false && _connected==false
* CONNECTING: _initialized==true && _connected==false (handshake sent)
* CONNECTED: _initialized==true && _connected==true (first successful connect)
* DISCONNECTING: _initialized==false && _connected==true (disconnect sent)
*/
this._initialized = false;
this._connected = false;
this._polling = false;
 
this.connectionTypes = new dojo.AdapterRegistry(true);
 
this.version = "1.0";
this.minimumVersion = "0.9";
this.clientId = null;
this.messageId = 0;
this.batch=0;
 
this._isXD = false;
this.handshakeReturn = null;
this.currentTransport = null;
this.url = null;
this.lastMessage = null;
this.topics = {};
this._messageQ = [];
this.handleAs="json-comment-optional";
this.advice;
this.pendingSubscriptions = {}
this.pendingUnsubscriptions = {}
 
this._subscriptions = [];
 
this.tunnelInit = function(childLocation, childDomain){
// placeholder
}
 
this.tunnelCollapse = function(){
console.debug("tunnel collapsed!");
// placeholder
}
 
this.init = function(root, props, bargs){
// FIXME: if the root isn't from the same host, we should automatically
// try to select an XD-capable transport
props = props||{};
// go ask the short bus server what we can support
props.version = this.version;
props.minimumVersion = this.minimumVersion;
props.channel = "/meta/handshake";
props.id = ""+this.messageId++;
 
this.url = root||djConfig["cometdRoot"];
if(!this.url){
console.debug("no cometd root specified in djConfig and no root passed");
return;
}
 
// Are we x-domain? borrowed from dojo.uri.Uri in lieu of fixed host and port properties
var regexp = "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$";
var r = (""+window.location).match(new RegExp(regexp));
if(r[4]){
var tmp = r[4].split(":");
var thisHost = tmp[0];
var thisPort = tmp[1]||"80"; // FIXME: match 443
 
r = this.url.match(new RegExp(regexp));
if(r[4]){
tmp = r[4].split(":");
var urlHost = tmp[0];
var urlPort = tmp[1]||"80";
this._isXD = ((urlHost != thisHost)||(urlPort != thisPort));
}
}
 
if(!this._isXD){
if(props.ext){
if(props.ext["json-comment-filtered"]!==true && props.ext["json-comment-filtered"]!==false){
props.ext["json-comment-filtered"] = true;
}
}else{
props.ext = { "json-comment-filtered": true };
}
}
 
var bindArgs = {
url: this.url,
handleAs: this.handleAs,
content: { "message": dojo.toJson([props]) },
load: dojo.hitch(this, "finishInit"),
error: function(e){ console.debug("handshake error!:", e); }
};
 
if(bargs){
dojo.mixin(bindArgs, bargs);
}
this._props=props;
this._initialized=true;
this.batch=0;
this.startBatch();
// if xdomain, then we assume jsonp for handshake
if(this._isXD){
bindArgs.callbackParamName="jsonp";
return dojo.io.script.get(bindArgs);
}
return dojo.xhrPost(bindArgs);
}
 
this.finishInit = function(data){
data = data[0];
this.handshakeReturn = data;
// pick a transport
if(data["advice"]){
this.advice = data.advice;
}
if(!data.successful){
console.debug("cometd init failed");
if(this.advice && this.advice["reconnect"]=="none"){
return;
}
 
if( this.advice && this.advice["interval"] && this.advice.interval>0 ){
var cometd=this;
setTimeout(function(){ cometd.init(cometd.url,cometd._props); }, this.advice.interval);
}else{
this.init(this.url,this._props);
}
 
return;
}
if(data.version < this.minimumVersion){
console.debug("cometd protocol version mismatch. We wanted", this.minimumVersion, "but got", data.version);
return;
}
this.currentTransport = this.connectionTypes.match(
data.supportedConnectionTypes,
data.version,
this._isXD
);
this.currentTransport._cometd = this;
this.currentTransport.version = data.version;
this.clientId = data.clientId;
this.tunnelInit = dojo.hitch(this.currentTransport, "tunnelInit");
this.tunnelCollapse = dojo.hitch(this.currentTransport, "tunnelCollapse");
 
this.currentTransport.startup(data);
}
 
// public API functions called by cometd or by the transport classes
this.deliver = function(messages){
// console.debug(messages);
dojo.forEach(messages, this._deliver, this);
return messages;
}
 
this._deliver = function(message){
// dipatch events along the specified path
 
if(!message["channel"]){
if(message["success"] !== true){
console.debug("cometd error: no channel for message!", message);
return;
}
}
this.lastMessage = message;
 
if(message.advice){
this.advice = message.advice; // TODO maybe merge?
}
 
// check to see if we got a /meta channel message that we care about
if( (message["channel"]) &&
(message.channel.length > 5)&&
(message.channel.substr(0, 5) == "/meta")){
// check for various meta topic actions that we need to respond to
switch(message.channel){
case "/meta/connect":
if(message.successful && !this._connected){
this._connected = this._initialized;
this.endBatch();
} else if(!this._initialized){
this._connected = false; // finish disconnect
}
break;
case "/meta/subscribe":
var pendingDef = this.pendingSubscriptions[message.subscription];
if(!message.successful){
if(pendingDef){
pendingDef.errback(new Error(message.error));
delete this.pendingSubscriptions[message.subscription];
}
return;
}
dojox.cometd.subscribed(message.subscription, message);
if(pendingDef){
pendingDef.callback(true);
delete this.pendingSubscriptions[message.subscription];
}
break;
case "/meta/unsubscribe":
var pendingDef = this.pendingUnsubscriptions[message.subscription];
if(!message.successful){
if(pendingDef){
pendingDef.errback(new Error(message.error));
delete this.pendingUnsubscriptions[message.subscription];
}
return;
}
this.unsubscribed(message.subscription, message);
if(pendingDef){
pendingDef.callback(true);
delete this.pendingUnsubscriptions[message.subscription];
}
break;
}
}
// send the message down for processing by the transport
this.currentTransport.deliver(message);
 
if(message.data){
// dispatch the message to any locally subscribed listeners
var tname = "/cometd"+message.channel;
dojo.publish(tname, [ message ]);
}
}
 
this.disconnect = function(){
dojo.forEach(this._subscriptions, dojo.unsubscribe);
this._subscriptions = [];
this._messageQ = [];
if(this._initialized && this.currentTransport){
this._initialized=false;
this.currentTransport.disconnect();
}
this._initialized=false;
if(!this._polling)
this._connected=false;
}
 
// public API functions called by end users
this.publish = function(/*string*/channel, /*object*/data, /*object*/properties){
// summary:
// publishes the passed message to the cometd server for delivery
// on the specified topic
// channel:
// the destination channel for the message
// data:
// a JSON object containing the message "payload"
// properties:
// Optional. Other meta-data to be mixed into the top-level of the
// message
var message = {
data: data,
channel: channel
};
if(properties){
dojo.mixin(message, properties);
}
this._sendMessage(message);
}
 
this._sendMessage = function(/* object */ message){
if(this.currentTransport && this._connected && this.batch==0){
return this.currentTransport.sendMessages([message]);
}
else{
this._messageQ.push(message);
}
}
 
this.subscribe = function( /*string */ channel,
/*object, optional*/ objOrFunc,
/*string, optional*/ funcName){ // return: boolean
// summary:
// inform the server of this client's interest in channel
// channel:
// name of the cometd channel to subscribe to
// objOrFunc:
// an object scope for funcName or the name or reference to a
// function to be called when messages are delivered to the
// channel
// funcName:
// the second half of the objOrFunc/funcName pair for identifying
// a callback function to notifiy upon channel message delivery
 
if(this.pendingSubscriptions[channel]){
// We already asked to subscribe to this channel, and
// haven't heard back yet. Fail the previous attempt.
var oldDef = this.pendingSubscriptions[channel];
oldDef.cancel();
delete this.pendingSubscriptions[channel];
}
 
var pendingDef = new dojo.Deferred();
this.pendingSubscriptions[channel] = pendingDef;
 
if(objOrFunc){
var tname = "/cometd"+channel;
if(this.topics[tname]){
dojo.unsubscribe(this.topics[tname]);
}
var topic = dojo.subscribe(tname, objOrFunc, funcName);
this.topics[tname] = topic;
}
 
this._sendMessage({
channel: "/meta/subscribe",
subscription: channel
});
 
return pendingDef;
 
}
 
this.subscribed = function( /*string*/ channel,
/*obj*/ message){
}
 
 
this.unsubscribe = function(/*string*/ channel){ // return: boolean
// summary:
// inform the server of this client's disinterest in channel
// channel:
// name of the cometd channel to unsubscribe from
 
if(this.pendingUnsubscriptions[channel]){
// We already asked to unsubscribe from this channel, and
// haven't heard back yet. Fail the previous attempt.
var oldDef = this.pendingUnsubscriptions[channel];
oldDef.cancel();
delete this.pendingUnsubscriptions[channel];
}
 
var pendingDef = new dojo.Deferred();
this.pendingUnsubscriptions[channel] = pendingDef;
 
var tname = "/cometd"+channel;
if(this.topics[tname]){
dojo.unsubscribe(this.topics[tname]);
}
 
this._sendMessage({
channel: "/meta/unsubscribe",
subscription: channel
});
 
return pendingDef;
 
}
 
this.unsubscribed = function( /*string*/ channel,
/*obj*/ message){
}
 
this.startBatch = function(){
this.batch++;
}
 
this.endBatch = function(){
if(--this.batch <= 0 && this.currentTransport && this._connected){
this.batch=0;
 
var messages=this._messageQ;
this._messageQ=[];
if(messages.length>0){
this.currentTransport.sendMessages(messages);
}
}
}
this._onUnload = function(){
// make this the last of the onUnload method
dojo.addOnUnload(dojox.cometd,"disconnect");
}
}
 
/*
transport objects MUST expose the following methods:
- check
- startup
- sendMessages
- deliver
- disconnect
optional, standard but transport dependent methods are:
- tunnelCollapse
- tunnelInit
 
Transports SHOULD be namespaced under the cometd object and transports MUST
register themselves with cometd.connectionTypes
 
here's a stub transport defintion:
 
cometd.blahTransport = new function(){
this._connectionType="my-polling";
this._cometd=null;
this.lastTimestamp = null;
 
this.check = function(types, version, xdomain){
// summary:
// determines whether or not this transport is suitable given a
// list of transport types that the server supports
return dojo.lang.inArray(types, "blah");
}
 
this.startup = function(){
if(dojox.cometd._polling){ return; }
// FIXME: fill in startup routine here
dojox.cometd._polling = true;
}
 
this.sendMessages = function(message){
// FIXME: fill in message array sending logic
}
 
this.deliver = function(message){
if(message["timestamp"]){
this.lastTimestamp = message.timestamp;
}
if( (message.channel.length > 5)&&
(message.channel.substr(0, 5) == "/meta")){
// check for various meta topic actions that we need to respond to
// switch(message.channel){
// case "/meta/connect":
// // FIXME: fill in logic here
// break;
// // case ...: ...
// }
}
}
 
this.disconnect = function(){
}
}
cometd.connectionTypes.register("blah", cometd.blahTransport.check, cometd.blahTransport);
*/
 
dojox.cometd.longPollTransport = new function(){
this._connectionType="long-polling";
this._cometd=null;
this.lastTimestamp = null;
 
this.check = function(types, version, xdomain){
return ((!xdomain)&&(dojo.indexOf(types, "long-polling") >= 0));
}
 
this.tunnelInit = function(){
if(this._cometd._polling){ return; }
this.openTunnelWith({
message: dojo.toJson([
{
channel: "/meta/connect",
clientId: this._cometd.clientId,
connectionType: this._connectionType,
id: ""+this._cometd.messageId++
}
])
});
}
 
this.tunnelCollapse = function(){
if(!this._cometd._polling){
// try to restart the tunnel
this._cometd._polling = false;
 
// TODO handle transport specific advice
 
if(this._cometd["advice"]){
if(this._cometd.advice["reconnect"]=="none"){
return;
}
 
if( (this._cometd.advice["interval"])&&
(this._cometd.advice.interval>0) ){
var transport = this;
setTimeout(function(){ transport._connect(); },
this._cometd.advice.interval);
}else{
this._connect();
}
}else{
this._connect();
}
}
}
 
this._connect = function(){
if( (this._cometd["advice"])&&
(this._cometd.advice["reconnect"]=="handshake")
){
this._cometd.init(this._cometd.url,this._cometd._props);
}else if(this._cometd._connected){
this.openTunnelWith({
message: dojo.toJson([
{
channel: "/meta/connect",
connectionType: this._connectionType,
clientId: this._cometd.clientId,
timestamp: this.lastTimestamp,
id: ""+this._cometd.messageId++
}
])
});
}
}
 
this.deliver = function(message){
// console.debug(message);
if(message["timestamp"]){
this.lastTimestamp = message.timestamp;
}
}
 
this.openTunnelWith = function(content, url){
// console.debug("openTunnelWith:", content, (url||cometd.url));
var d = dojo.xhrPost({
url: (url||this._cometd.url),
content: content,
handleAs: this._cometd.handleAs,
load: dojo.hitch(this, function(data){
// console.debug(evt.responseText);
// console.debug(data);
this._cometd._polling = false;
this._cometd.deliver(data);
this.tunnelCollapse();
}),
error: function(err){
console.debug("tunnel opening failed:", err);
dojo.cometd._polling = false;
 
// TODO - follow advice to reconnect or rehandshake?
}
});
this._cometd._polling = true;
}
 
this.sendMessages = function(messages){
for(var i=0; i<messages.length; i++){
messages[i].clientId = this._cometd.clientId;
messages[i].id = ""+this._cometd.messageId++;
}
return dojo.xhrPost({
url: this._cometd.url||djConfig["cometdRoot"],
handleAs: this._cometd.handleAs,
load: dojo.hitch(this._cometd, "deliver"),
content: {
message: dojo.toJson(messages)
}
});
}
 
this.startup = function(handshakeData){
if(this._cometd._connected){ return; }
this.tunnelInit();
}
 
this.disconnect = function(){
dojo.xhrPost({
url: this._cometd.url||djConfig["cometdRoot"],
handleAs: this._cometd.handleAs,
content: {
message: dojo.toJson([{
channel: "/meta/disconnect",
clientId: this._cometd.clientId,
id: ""+this._cometd.messageId++
}])
}
});
}
}
 
dojox.cometd.callbackPollTransport = new function(){
this._connectionType = "callback-polling";
this._cometd = null;
this.lastTimestamp = null;
 
this.check = function(types, version, xdomain){
// we handle x-domain!
return (dojo.indexOf(types, "callback-polling") >= 0);
}
 
this.tunnelInit = function(){
if(this._cometd._polling){ return; }
this.openTunnelWith({
message: dojo.toJson([
{
channel: "/meta/connect",
clientId: this._cometd.clientId,
connectionType: this._connectionType,
id: ""+this._cometd.messageId++
}
])
});
}
 
this.tunnelCollapse = dojox.cometd.longPollTransport.tunnelCollapse;
this._connect = dojox.cometd.longPollTransport._connect;
this.deliver = dojox.cometd.longPollTransport.deliver;
 
this.openTunnelWith = function(content, url){
// create a <script> element to generate the request
dojo.io.script.get({
load: dojo.hitch(this, function(data){
this._cometd._polling = false;
this._cometd.deliver(data);
this.tunnelCollapse();
}),
error: function(){
this._cometd._polling = false;
console.debug("tunnel opening failed");
},
url: (url||this._cometd.url),
content: content,
callbackParamName: "jsonp"
});
this._cometd._polling = true;
}
 
this.sendMessages = function(/*array*/ messages){
for(var i=0; i<messages.length; i++){
messages[i].clientId = this._cometd.clientId;
messages[i].id = ""+this._cometd.messageId++;
}
var bindArgs = {
url: this._cometd.url||djConfig["cometdRoot"],
load: dojo.hitch(this._cometd, "deliver"),
callbackParamName: "jsonp",
content: { message: dojo.toJson( messages ) }
};
return dojo.io.script.get(bindArgs);
}
 
this.startup = function(handshakeData){
if(this._cometd._connected){ return; }
this.tunnelInit();
}
 
this.disconnect = dojox.cometd.longPollTransport.disconnect;
 
this.disconnect = function(){
dojo.io.script.get({
url: this._cometd.url||djConfig["cometdRoot"],
callbackParamName: "jsonp",
content: {
message: dojo.toJson([{
channel: "/meta/disconnect",
clientId: this._cometd.clientId,
id: ""+this._cometd.messageId++
}])
}
});
}
}
dojox.cometd.connectionTypes.register("long-polling", dojox.cometd.longPollTransport.check, dojox.cometd.longPollTransport);
dojox.cometd.connectionTypes.register("callback-polling", dojox.cometd.callbackPollTransport.check, dojox.cometd.callbackPollTransport);
 
dojo.addOnUnload(dojox.cometd,"_onUnload");
 
 
}