Blame | Last modification | View Log | RSS feed
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 transportprops = props||{};// go ask the short bus server what we can supportprops.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 propertiesvar 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 443r = 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 handshakeif(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 transportif(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 classesthis.deliver = function(messages){// console.debug(messages);dojo.forEach(messages, this._deliver, this);return messages;}this._deliver = function(message){// dipatch events along the specified pathif(!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 aboutif( (message["channel"]) &&(message.channel.length > 5)&&(message.channel.substr(0, 5) == "/meta")){// check for various meta topic actions that we need to respond toswitch(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 transportthis.currentTransport.deliver(message);if(message.data){// dispatch the message to any locally subscribed listenersvar 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 usersthis.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// messagevar 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 deliveryif(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 fromif(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 methoddojo.addOnUnload(dojox.cometd,"disconnect");}}/*transport objects MUST expose the following methods:- check- startup- sendMessages- deliver- disconnectoptional, standard but transport dependent methods are:- tunnelCollapse- tunnelInitTransports SHOULD be namespaced under the cometd object and transports MUSTregister themselves with cometd.connectionTypeshere'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 supportsreturn dojo.lang.inArray(types, "blah");}this.startup = function(){if(dojox.cometd._polling){ return; }// FIXME: fill in startup routine heredojox.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 tunnelthis._cometd._polling = false;// TODO handle transport specific adviceif(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 requestdojo.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");}