123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680 |
- 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");
- }
|