diff options
Diffstat (limited to 'includes/js/dojox/cometd')
-rw-r--r-- | includes/js/dojox/cometd/README | 29 | ||||
-rw-r--r-- | includes/js/dojox/cometd/_base.js | 950 | ||||
-rw-r--r-- | includes/js/dojox/cometd/timestamp.js | 9 | ||||
-rw-r--r-- | includes/js/dojox/cometd/timesync.js | 126 |
4 files changed, 1114 insertions, 0 deletions
diff --git a/includes/js/dojox/cometd/README b/includes/js/dojox/cometd/README new file mode 100644 index 0000000..05a42a2 --- /dev/null +++ b/includes/js/dojox/cometd/README @@ -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. diff --git a/includes/js/dojox/cometd/_base.js b/includes/js/dojox/cometd/_base.js new file mode 100644 index 0000000..eb129ee --- /dev/null +++ b/includes/js/dojox/cometd/_base.js @@ -0,0 +1,950 @@ +if(!dojo._hasResource["dojox.cometd._base"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.cometd._base"] = true; +dojo.provide("dojox.cometd._base"); +dojo.require("dojo.AdapterRegistry"); +dojo.require("dojo.io.script"); + + +/* + * 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. + * + * extensions modules may be loaded (eg "dojox.cometd.timestamp", that use + * the cometd._extendInList and cometd._extendOutList fields to provide functions + * that extend and handling incoming and outgoing messages. + */ + +dojox.cometd = new function(){ + + // cometd states: + + // alex; OMG, these "constants" need to die. Java truly is a degenerative disease. + this.DISCONNECTED = "DISCONNECTED"; // _initialized==false && _connected==false + this.CONNECTING = "CONNECTING"; // _initialized==true && _connected==false (handshake sent) + this.CONNECTED = "CONNECTED"; // _initialized==true && _connected==true (first successful connect) + this.DISCONNECTING = "DISCONNECING"; // _initialized==false && _connected==true (disconnect sent) + + this._initialized = false; + this._connected = false; + this._polling = false; + + this.expectedNetworkDelay = 5000; // expected max network delay + this.connectTimeout = 0; // If set, used as ms to wait for a connect response and sent as the advised timeout + + 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._messageQ = []; + this.handleAs = "json-comment-optional"; + this._advice = {}; + this._backoffInterval = 0; + this._backoffIncrement = 1000; + this._backoffMax = 60000; + this._deferredSubscribes = {}; + this._deferredUnsubscribes = {}; + this._subscriptions = []; + this._extendInList = []; // List of functions invoked before delivering messages + this._extendOutList = []; // List of functions invoked before sending messages + + this.state = function() { + return this._initialized ? + (this._connected ? "CONNECTED" : "CONNECTING") : + ( this._connected ? "DISCONNECTING" : "DISCONNECTED"); + } + + this.init = function( /*String*/ root, + /*Object?*/ props, + /*Object?*/ bargs){ // return: dojo.Deferred + // summary: + // Initialize the cometd implementation of the Bayeux protocol + // description: + // Initialize the cometd implementation of the Bayeux protocol by + // sending a handshake message. The cometd state will be changed to CONNECTING + // until a handshake response is received and the first successful connect message + // has returned. + // The protocol state changes may be monitored + // by subscribing to the dojo topic "/cometd/meta" where events are + // published in the form {cometd:this,action:"handshake",successful:true,state:this.state()} + // root: + // The URL of the cometd server. If the root is absolute, the host + // is examined to determine if xd transport is needed. Otherwise the + // same domain is assumed. + // props: + // An optional object that is used as the basis of the handshake message + // bargs: + // An optional object of bind args mixed in with the send of the handshake + // example: + // | dojox.cometd.init("/cometd"); + // | dojox.cometd.init("http://xdHost/cometd",{ext:{user:"fred",pwd:"secret"}}); + + + // 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||dojo.config["cometdRoot"]; + if(!this.url){ + console.debug("no cometd root specified in djConfig and no root passed"); + return null; + } + + // Are we x-domain? borrowed from dojo.uri.Uri in lieu of fixed host and port properties + var regexp = "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$"; + var parts = (""+window.location).match(new RegExp(regexp)); + if(parts[4]){ + var tmp = parts[4].split(":"); + var thisHost = tmp[0]; + var thisPort = tmp[1]||"80"; // FIXME: match 443 + + parts = this.url.match(new RegExp(regexp)); + if(parts[4]){ + tmp = parts[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 }; + } + props.supportedConnectionTypes = dojo.map(this.connectionTypes.pairs, "return item[0]"); + } + + props = this._extendOut(props); + + var bindArgs = { + url: this.url, + handleAs: this.handleAs, + content: { "message": dojo.toJson([props]) }, + load: dojo.hitch(this,function(msg){ + this._backon(); + this._finishInit(msg); + }), + error: dojo.hitch(this,function(e){ + console.debug("handshake error!:",e); + this._backoff(); + this._finishInit([{}]); + }) + }; + + if(bargs){ + dojo.mixin(bindArgs, bargs); + } + this._props = props; + for(var tname in this._subscriptions){ + for(var sub in this._subscriptions[tname]){ + if(this._subscriptions[tname][sub].topic){ + dojo.unsubscribe(this._subscriptions[tname][sub].topic); + } + } + } + this._messageQ = []; + this._subscriptions = []; + this._initialized = true; + this.batch = 0; + this.startBatch(); + + var r; + // if xdomain, then we assume jsonp for handshake + if(this._isXD){ + bindArgs.callbackParamName = "jsonp"; + r = dojo.io.script.get(bindArgs); + }else{ + r = dojo.xhrPost(bindArgs); + } + dojo.publish("/cometd/meta", [ + { + cometd: this, + action: "handshake", + successful: true, + state: this.state() + } + ]); + return r; + } + + + this.publish = function(/*String*/ channel, /*Object*/ data, /*Object?*/ props){ + // 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(props){ + dojo.mixin(message, props); + } + this._sendMessage(message); + } + + + this.subscribe = function( /*String */ channel, + /*Object */ objOrFunc, + /*String */ funcName, + /*Object?*/ props){ // return: dojo.Deferred + // summary: + // inform the server of this client's interest in channel + // description: + // `dojox.cometd.subscribe()` handles all the hard work of telling + // the server that we want to be notified when events are + // published on a particular topic. `subscribe` accepts a function + // to handle messages and returns a `dojo.Deferred` object which + // has an extra property added to it which makes it suitable for + // passing to `dojox.cometd.unsubscribe()` as a "subscription + // handle" (much like the handle object that `dojo.connect()` + // produces and which `dojo.disconnect()` expects). + // + // Note that of a subscription is registered before a connection + // with the server is established, events sent before the + // connection is established will not be delivered to this client. + // The deferred object which `subscribe` returns will callback + // when the server successfuly acknolwedges receipt of our + // "subscribe" request. + // 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 + // example: + // Simple subscribe use-case + // | dojox.cometd.init("http://myserver.com:8080/cometd"); + // | // log out all incoming messages on /foo/bar + // | dojox.cometd.subscribe("/foo/bar", console, "debug"); + // example: + // Subscribe before connection is initialized + // | dojox.cometd.subscribe("/foo/bar", console, "debug"); + // | dojox.cometd.init("http://myserver.com:8080/cometd"); + // example: + // Subscribe an unsubscribe + // | dojox.cometd.init("http://myserver.com:8080/cometd"); + // | var h = dojox.cometd.subscribe("/foo/bar", console, "debug"); + // | dojox.cometd.unsubscribe(h); + // example: + // Listen for successful subscription: + // | dojox.cometd.init("http://myserver.com:8080/cometd"); + // | var h = dojox.cometd.subscribe("/foo/bar", console, "debug"); + // | h.addCallback(function(){ + // | console.debug("subscription to /foo/bar established"); + // | }); + + props = props||{}; + if(objOrFunc){ + var tname = "/cometd"+channel; + var subs = this._subscriptions[tname]; + if(!subs || subs.length==0){ + subs = []; + props.channel = "/meta/subscribe"; + props.subscription = channel; + this._sendMessage(props); + + var _ds = this._deferredSubscribes; + if(_ds[channel]){ + _ds[channel].cancel(); + delete _ds[channel]; + } + _ds[channel] = new dojo.Deferred(); + } + + for(var i in subs){ + if( subs[i].objOrFunc === objOrFunc && (!subs[i].funcName&&!funcName||subs[i].funcName==funcName) ){ + return null; + } + } + + var topic = dojo.subscribe(tname, objOrFunc, funcName); + subs.push({ + topic: topic, + objOrFunc: objOrFunc, + funcName: funcName + }); + this._subscriptions[tname] = subs; + } + var ret = this._deferredSubscribes[channel]||{}; + ret.args = dojo._toArray(arguments); + return ret; // dojo.Deferred + } + + + + this.unsubscribe = function( /*String*/ channel, + /*Object?*/ objOrFunc, + /*String?*/ funcName, + /*Object?*/ props){ + // summary: + // inform the server of this client's disinterest in channel + // channel: + // name of the cometd channel to unsubscribe from + // objOrFunc: + // an object scope for funcName or the name or reference to a + // function to be called when messages are delivered to the + // channel. If null then all subscribers to the channel are unsubscribed. + // funcName: + // the second half of the objOrFunc/funcName pair for identifying + // a callback function to notifiy upon channel message delivery + + if( + (arguments.length == 1) && + (!dojo.isString(channel)) && + (channel.args) + ){ + // it's a subscription handle, unroll + return this.unsubscribe.apply(this, channel.args); + } + + var tname = "/cometd"+channel; + var subs = this._subscriptions[tname]; + if(!subs || subs.length==0){ + return null; + } + + var s=0; + for(var i in subs){ + var sb = subs[i]; + if( (!objOrFunc) || + ( + sb.objOrFunc===objOrFunc && + (!sb.funcName && !funcName || sb.funcName==funcName) + ) + ){ + dojo.unsubscribe(subs[i].topic); + delete subs[i]; + }else{ + s++; + } + } + + if(s==0){ + props = props||{}; + props.channel = "/meta/unsubscribe"; + props.subscription = channel; + delete this._subscriptions[tname]; + this._sendMessage(props); + this._deferredUnsubscribes[channel] = new dojo.Deferred(); + if (this._deferredSubscribes[channel]){ + this._deferredSubscribes[channel].cancel(); + delete this._deferredSubscribes[channel]; + } + } + return this._deferredUnsubscribes[channel]; // dojo.Deferred + } + + + this.disconnect = function(){ + // summary: + // Disconnect from the server. + // description: + // Disconnect from the server by sending a disconnect message + // example: + // | dojox.cometd.disconnect(); + + for(var tname in this._subscriptions){ + for(var sub in this._subscriptions[tname]){ + if(this._subscriptions[tname][sub].topic){ + dojo.unsubscribe(this._subscriptions[tname][sub].topic); + } + } + } + this._subscriptions = []; + this._messageQ = []; + if(this._initialized && this.currentTransport){ + this._initialized=false; + this.currentTransport.disconnect(); + } + if(!this._polling) { + this._connected=false; + dojo.publish("/cometd/meta", [{cometd:this,action:"connect",successful:false,state:this.state()}]); + } + this._initialized=false; + dojo.publish("/cometd/meta", [{cometd:this,action:"disconnect",successful:true,state:this.state()}]); + } + + + // public extension points + + this.subscribed = function( /*String*/channel, /*Object*/message){ } + + this.unsubscribed = function(/*String*/channel, /*Object*/message){ } + + + // private methods (TODO name all with leading _) + + this.tunnelInit = function(childLocation, childDomain){ + // placeholder - replaced by _finishInit + } + + this.tunnelCollapse = function(){ + // placeholder - replaced by _finishInit + } + + this._backoff = function(){ + if(!this._advice){ + this._advice={reconnect:"retry",interval:0}; + } + else if(!this._advice.interval){ + this._advice.interval=0; + } + if(this._backoffInterval<this._backoffMax){ + this._backoffInterval+=this._backoffIncrement; + } + } + + this._backon = function(){ + this._backoffInterval=0; + } + + this._interval = function(){ + var i=this._backoffInterval+(this._advice?(this._advice.interval?this._advice.interval:0):0); + if (i>0) + console.debug("Retry in interval+backoff="+this._advice.interval+"+"+this._backoffInterval+"="+i+"ms"); + return i; + } + + this._finishInit = function(data){ + // summary: + // Handle the handshake return from the server and initialize + // connection if all is OK + data = data[0]; + this.handshakeReturn = data; + + // remember any advice + if(data["advice"]){ + this._advice = data.advice; + } + + var successful=data.successful?data.successful:false; + + // check version + if(data.version < this.minimumVersion){ + console.debug("cometd protocol version mismatch. We wanted", this.minimumVersion, "but got", data.version); + successful=false; + this._advice.reconnect="none"; + } + + // If all OK + if(successful){ + // pick a transport + this.currentTransport = this.connectionTypes.match( + data.supportedConnectionTypes, + data.version, + this._isXD + ); + // initialize the transport + 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); + } + + dojo.publish("/cometd/meta", [{cometd:this,action:"handshook",successful:successful,state:this.state()}]); + + // If there is a problem + if(!successful){ + console.debug("cometd init failed"); + // follow advice + if(this._advice && this._advice["reconnect"]=="none"){ + console.debug("cometd reconnect: none"); + }else{ + setTimeout(dojo.hitch(this, "init", this.url, this._props),this._interval()); + } + } + } + + // fixme: lots of repeated code...why? + + this._extendIn = function(message){ + // Handle extensions for inbound messages + dojo.forEach(dojox.cometd._extendInList, function(f){ + message = f(message)||message; + }); + return message; + } + + this._extendOut = function(message){ + // Handle extensions for inbound messages + dojo.forEach(dojox.cometd._extendOutList, function(f){ + message = f(message)||message; + }); + return message; + } + + + this.deliver = function(messages){ + dojo.forEach(messages, this._deliver, this); + return messages; + } + + this._deliver = function(message){ + // dipatch events along the specified path + + message=this._extendIn(message); + + 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 + var deferred=null; + 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 + } + dojo.publish("/cometd/meta",[{cometd:this,action:"connect",successful:message.successful,state:this.state()}]); + break; + case "/meta/subscribe": + deferred = this._deferredSubscribes[message.subscription]; + if(!message.successful){ + if(deferred){ + deferred.errback(new Error(message.error)); + } + this.currentTransport.cancelConnect(); + return; + } + dojox.cometd.subscribed(message.subscription, message); + if(deferred){ + deferred.callback(true); + } + break; + case "/meta/unsubscribe": + deferred = this._deferredUnsubscribes[message.subscription]; + if(!message.successful){ + if(deferred){ + deferred.errback(new Error(message.error)); + } + this.currentTransport.cancelConnect(); + return; + } + this.unsubscribed(message.subscription, message); + if(deferred){ + deferred.callback(true); + } + break; + default: + if(message.successful && !message.successful){ + this.currentTransport.cancelConnect(); + return; + } + } + } + + // send the message down for processing by the transport + this.currentTransport.deliver(message); + + if(message.data){ + // dispatch the message to any locally subscribed listeners + try { + var messages=[message]; + + // Determine target topic + var tname="/cometd"+message.channel; + + // Deliver to globs that apply to target topic + var tnameParts=message.channel.split("/"); + var tnameGlob="/cometd"; + for (var i=1;i<tnameParts.length-1;i++) { + dojo.publish(tnameGlob+"/**",messages); + tnameGlob+="/"+tnameParts[i]; + } + dojo.publish(tnameGlob+"/**",messages); + dojo.publish(tnameGlob+"/*",messages); + + // deliver to target topic + dojo.publish(tname,messages); + }catch(e){ + console.debug(e); + } + } + } + + this._sendMessage = function(/* object */ message){ + // console.debug(this.currentTransport, this._connected, this.batch); + // if(this.currentTransport && this._connected && !this.batch){ + if(this.currentTransport && !this.batch){ + return this.currentTransport.sendMessages([message]); + }else{ + this._messageQ.push(message); + return null; + } + } + + 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"); + } + + this._connectTimeout = function(){ + // return the connect timeout in ms, calculated as the minimum of the advised timeout + // and the configured timeout. Else 0 to indicate no client side timeout + var _advised=0; + if (this._advice && this._advice.timeout && this.expectedNetworkDelay>0) + _advised=this._advice.timeout + this.expectedNetworkDelay; + + if (this.connectTimeout>0 && this.connectTimeout<_advised) + return this.connectTimeout; + + return 0; + } +} + +/* +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){ + } + + this.disconnect = function(){ + // send orderly disconnect message + } + + this.cancelConnect = function(){ + // cancel the current connection + } +} +cometd.connectionTypes.register("blah", cometd.blahTransport.check, cometd.blahTransport); +*/ + +dojox.cometd.longPollTransport = new function(){ + this._connectionType="long-polling"; + this._cometd=null; + + this.check = function(types, version, xdomain){ + return ((!xdomain)&&(dojo.indexOf(types, "long-polling") >= 0)); + } + + this.tunnelInit = function(){ + var message = { + channel: "/meta/connect", + clientId: this._cometd.clientId, + connectionType: this._connectionType, + id: ""+this._cometd.messageId++ + }; + message=this._cometd._extendOut(message); + this.openTunnelWith({message: dojo.toJson([message])}); + } + + this.tunnelCollapse = function(){ + // TODO handle transport specific advice + + if(!this._cometd._initialized){ return; } + + if(this._cometd._advice && this._cometd._advice["reconnect"]=="none"){ + console.debug("cometd reconnect: none"); + return; + } + setTimeout(dojo.hitch(this,function(){ this._connect(); }),this._cometd._interval()); + } + + this._connect = function(){ + if(!this._cometd._initialized){ return; } + if(this._cometd._polling) { + console.debug("wait for poll to complete or fail"); + return; + } + + if((this._cometd._advice) && (this._cometd._advice["reconnect"]=="handshake")){ + this._cometd._connected=false; + this._initialized = false; + this._cometd.init(this._cometd.url,this._cometd._props); + }else if(this._cometd._connected){ + var message={ + channel: "/meta/connect", + connectionType: this._connectionType, + clientId: this._cometd.clientId, + id: ""+this._cometd.messageId++ + }; + if (this._cometd.connectTimeout>this._cometd.expectedNetworkDelay) + message.advice={timeout:(this._cometd.connectTimeout-this._cometd.expectedNetworkDelay)}; + + message=this._cometd._extendOut(message); + this.openTunnelWith({message: dojo.toJson([message])}); + } + } + + this.deliver = function(message){ + // Nothing to do + } + + this.openTunnelWith = function(content, url){ + this._cometd._polling = true; + var post = { + url: (url||this._cometd.url), + content: content, + handleAs: this._cometd.handleAs, + load: dojo.hitch(this, function(data){ + this._cometd._polling=false; + this._cometd.deliver(data); + this._cometd._backon(); + this.tunnelCollapse(); + }), + error: dojo.hitch(this, function(err){ + this._cometd._polling=false; + console.debug("tunnel opening failed:", err); + dojo.publish("/cometd/meta", [{cometd:this._cometd,action:"connect",successful:false,state:this._cometd.state()}]); + this._cometd._backoff(); + this.tunnelCollapse(); + }) + }; + + var connectTimeout=this._cometd._connectTimeout(); + if (connectTimeout>0) + post.timeout=connectTimeout; + + this._poll = dojo.xhrPost(post); + } + + this.sendMessages = function(messages){ + for(var i=0; i<messages.length; i++){ + messages[i].clientId = this._cometd.clientId; + messages[i].id = ""+this._cometd.messageId++; + messages[i]=this._cometd._extendOut(messages[i]); + } + return dojo.xhrPost({ + url: this._cometd.url||dojo.config["cometdRoot"], + handleAs: this._cometd.handleAs, + load: dojo.hitch(this._cometd, "deliver"), + error: dojo.hitch(this, function(err){ + console.debug('dropped messages: ',messages); + }), + content: { + message: dojo.toJson(messages) + } + }); + } + + this.startup = function(handshakeData){ + if(this._cometd._connected){ return; } + this.tunnelInit(); + } + + this.disconnect = function(){ + var message={ + channel: "/meta/disconnect", + clientId: this._cometd.clientId, + id: ""+this._cometd.messageId++ + }; + message=this._cometd._extendOut(message); + dojo.xhrPost({ + url: this._cometd.url||dojo.config["cometdRoot"], + handleAs: this._cometd.handleAs, + content: { + message: dojo.toJson([message]) + } + }); + } + + this.cancelConnect = function(){ + if (this._poll) { + this._poll.cancel(); + this._cometd._polling=false; + dojo.debug("tunnel opening cancelled"); + dojo.event.topic.publish("/cometd/meta", {cometd:this._cometd,action:"connect",successful:false,state:this._cometd.state(),cancel:true}); + this._cometd._backoff(); + this.disconnect(); + this.tunnelCollapse(); + } + } + +} + +dojox.cometd.callbackPollTransport = new function(){ + this._connectionType = "callback-polling"; + this._cometd = null; + + this.check = function(types, version, xdomain){ + // we handle x-domain! + return (dojo.indexOf(types, "callback-polling") >= 0); + } + + this.tunnelInit = function(){ + var message = { + channel: "/meta/connect", + clientId: this._cometd.clientId, + connectionType: this._connectionType, + id: ""+this._cometd.messageId++ + }; + message = this._cometd._extendOut(message); + this.openTunnelWith({ + message: dojo.toJson([message]) + }); + } + + this.tunnelCollapse = dojox.cometd.longPollTransport.tunnelCollapse; + this._connect = dojox.cometd.longPollTransport._connect; + this.deliver = dojox.cometd.longPollTransport.deliver; + + this.openTunnelWith = function(content, url){ + this._cometd._polling = true; + var script = { + load: dojo.hitch(this, function(data){ + this._cometd._polling=false; + this._cometd.deliver(data); + this._cometd._backon(); + this.tunnelCollapse(); + }), + error: dojo.hitch(this, function(err){ + this._cometd._polling=false; + console.debug("tunnel opening failed:", err); + dojo.publish("/cometd/meta", [{cometd:this._cometd,action:"connect",successful:false,state:this._cometd.state()}]); + this._cometd._backoff(); + this.tunnelCollapse(); + }), + url: (url||this._cometd.url), + content: content, + callbackParamName: "jsonp" + }; + var connectTimeout=this._cometd._connectTimeout(); + if (connectTimeout>0) + script.timeout=connectTimeout; + dojo.io.script.get(script); + } + + 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++; + messages[i]=this._cometd._extendOut(messages[i]); + } + var bindArgs = { + url: this._cometd.url||dojo.config["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(){ + var message={ + channel:"/meta/disconnect", + clientId:this._cometd.clientId, + id:""+this._cometd.messageId++ + }; + message=this._cometd._extendOut(message); + dojo.io.script.get({ + url: this._cometd.url||dojo.config["cometdRoot"], + callbackParamName: "jsonp", + content: { + message: dojo.toJson([message]) + } + }); + } + + this.cancelConnect = function(){} +} +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"); + +} diff --git a/includes/js/dojox/cometd/timestamp.js b/includes/js/dojox/cometd/timestamp.js new file mode 100644 index 0000000..a5b8033 --- /dev/null +++ b/includes/js/dojox/cometd/timestamp.js @@ -0,0 +1,9 @@ +if(!dojo._hasResource["dojox.cometd.timestamp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.cometd.timestamp"] = true; +dojo.provide("dojox.cometd.timestamp"); +dojo.require("dojox.cometd"); + +// A cometd extension that adds a timestamp to every message +dojox.cometd._extendOutList.push(function(msg){msg.timestamp=new Date().toUTCString();return msg}); + +} diff --git a/includes/js/dojox/cometd/timesync.js b/includes/js/dojox/cometd/timesync.js new file mode 100644 index 0000000..44ef241 --- /dev/null +++ b/includes/js/dojox/cometd/timesync.js @@ -0,0 +1,126 @@ +if(!dojo._hasResource["dojox.cometd.timesync"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.cometd.timesync"] = true; +dojo.provide("dojox.cometd.timesync"); +dojo.require("dojox.cometd"); + +/** + * this file provides the time synchronization extension to cometd. + * Timesync allows the client and server to exchange time information on every + * handshake and connect message so that the client may calculate an approximate + * offset from it's own clock epoch to that of the server. + * + * With each handshake or connect, the extension sends timestamps within the + * ext field like: {ext:{timesync:{tc:12345567890},...},...} + * where ts is the timestamp in ms since 1970 of when the message was sent. + * + * A cometd server that supports timesync, should respond with and ext field + * like: {ext:{timesync:{tc:12345567890,ts:1234567900,p:123},...},...} + * where ts is the timestamp sent by the client, te is the timestamp on the server + * of when the message was received and p is the poll duration in ms - ie the + * time the server took before sending the response. + * + * On receipt of the response, the client is able to use current time to determine + * the total trip time, from which p is subtracted to determine an approximate + * two way network traversal time. The assumption is made that the network is + * symmetric for traversal time, so the offset between the two clocks is + * ts-tc-(now-tc-p)/2. In practise networks (and the cometd client/server software) + * is never perfectly symmetric, so accuracy is limited by the difference, which + * can be 10s of milliseconds. + * + * In order to smooth over any transient fluctuations, the extension keeps a sliding + * average of the offsets received. By default this is over 10 messages, but this can + * be changed with the dojox.cometd.timesync._window element. + */ +dojox.cometd.timesync= new function(){ + + this._window=10;// The window size for the sliding average of offset samples. + this._minWindow=4;// The window size for the sliding average of offset samples. + this._offsets=new Array(); // The samples used to calculate the average offset. + this.offset=0; // The offset in ms between the clients clock and the servers clock. Add this to the local + // time epoch to obtain server time. + this.samples=0; // The number of samples used to calculate the offset. If 0, the offset is not valid. + + this.getServerTime=function(){ // return: long + // Summary: + // Calculate the current time on the server + // + return new Date().getTime()+this.offset; + } + + this.getServerDate=function(){ // return: Date + // Summary: + // Calculate the current time on the server + // + return new Date(this.getServerTime()); + } + + this.setTimeout=function(/*function*/call,/*long|Date*/atTimeOrDate){ + // Summary: + // Set a timeout function relative to server time + // call: + // the function to call when the timeout occurs + // atTimeOrTime: + // a long timestamp or a Date representing the server time at + // which the timeout should occur. + + var ts=(atTimeOrDate instanceof Date)?atTimeOrDate.getTime():(0+atTimeOrDate); + var tc=ts-this.offset; + var interval=tc-new Date().getTime(); + if (interval<=0) + interval=1; + return setTimeout(call,interval); + } + + this._in=function(/*Object*/msg){ + // Summary: + // Handle incoming messages for the timesync extension. + // description: + // Look for ext:{timesync:{}} field and calculate offset if present. + // msg: + // The incoming bayeux message + + var channel=msg.channel; + if (channel && channel.indexOf('/meta/')==0){ + if (msg.ext && msg.ext.timesync){ + var sync=msg.ext.timesync; + var now=new Date().getTime(); + + this._offsets.push(sync.ts-sync.tc-(now-sync.tc-sync.p)/2); + if (this._offsets.length>this._window) + this._offsets.shift(); + this.samples++; + var total=0; + for (var i in this._offsets) + total+=this._offsets[i]; + this.offset=parseInt((total/this._offsets.length).toFixed()); + + if (this.samples<this._minWindow) + setTimeout(dojox._scopeName + ".cometd.publish('/meta/ping',null)",100); + } + } + return msg; + } + + this._out=function(msg){ + // Summary: + // Handle outgoing messages for the timesync extension. + // description: + // Look for handshake and connect messages and add the ext:{timesync:{}} fields + // msg: + // The outgoing bayeux message + + var channel=msg.channel; + if (channel && channel.indexOf('/meta/')==0){ + var now=new Date().getTime(); + if (!msg.ext) + msg.ext={}; + msg.ext.timesync={tc: now}; + } + return msg; + } +}; + +dojox.cometd._extendInList.push(dojo.hitch(dojox.cometd.timesync,"_in")); +dojox.cometd._extendOutList.push(dojo.hitch(dojox.cometd.timesync,"_out")); + +} |