| /* |
| Copyright (c) 2004-2006, The Dojo Foundation |
| All Rights Reserved. |
| |
| Licensed under the Academic Free License version 2.1 or above OR the |
| modified BSD license. For more information on Dojo licensing, see: |
| |
| http://dojotoolkit.org/community/licensing.shtml |
| */ |
| |
| dojo.require("dojo.io.common"); // io/common.js provides setIFrameSrc and the IO module |
| dojo.provide("dojo.io.cometd"); |
| dojo.require("dojo.AdapterRegistry"); |
| dojo.require("dojo.json"); |
| dojo.require("dojo.io.BrowserIO"); // we need XHR for the handshake, etc. |
| // FIXME: determine if we can use XMLHTTP to make x-domain posts despite not |
| // being able to hear back about the result |
| dojo.require("dojo.io.IframeIO"); |
| dojo.require("dojo.io.ScriptSrcIO"); // for x-domain long polling |
| dojo.require("dojo.io.cookie"); // for peering |
| dojo.require("dojo.event.*"); |
| dojo.require("dojo.lang.common"); |
| dojo.require("dojo.lang.func"); |
| |
| /* |
| * this file defines Comet protocol client. Actual message transport is |
| * deferred to one of several connection type implementations. The default is a |
| * forever-frame implementation. A single global object named "cometd" is |
| * used to mediate for these connection types in order to provide a stable |
| * interface. |
| */ |
| |
| // TODO: the auth handling in this file is a *mess*. It should probably live in |
| // the cometd object with the ability to mix in or call down to an auth-handler |
| // object, the prototypical variant of which is a no-op |
| |
| cometd = new function(){ |
| |
| this.initialized = false; |
| this.connected = false; |
| |
| this.connectionTypes = new dojo.AdapterRegistry(true); |
| |
| this.version = 0.1; |
| this.minimumVersion = 0.1; |
| this.clientId = null; |
| |
| this._isXD = false; |
| this.handshakeReturn = null; |
| this.currentTransport = null; |
| this.url = null; |
| this.lastMessage = null; |
| this.globalTopicChannels = {}; |
| this.backlog = []; |
| |
| this.tunnelInit = function(childLocation, childDomain){ |
| // placeholder |
| } |
| |
| this.tunnelCollapse = function(){ |
| dojo.debug("tunnel collapsed!"); |
| // placeholder |
| } |
| |
| this.init = function(props, root, 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"; |
| // FIXME: do we just assume that the props knows |
| // everything we care about WRT to auth? Should we be trying to |
| // call back into it for subsequent auth actions? Should we fire |
| // local auth functions to ask for/get auth data? |
| |
| // FIXME: what about ScriptSrcIO for x-domain comet? |
| this.url = root||djConfig["cometdRoot"]; |
| if(!this.url){ |
| dojo.debug("no cometd root specified in djConfig and no root passed"); |
| return; |
| } |
| |
| // FIXME: we need to select a way to handle JSONP-style stuff |
| // generically here. We already know if the server is gonna be on |
| // another domain (or can know it), so we should select appropriate |
| // negotiation methods here as well as in final transport type |
| // selection. |
| var bindArgs = { |
| url: this.url, |
| method: "POST", |
| mimetype: "text/json", |
| load: dojo.lang.hitch(this, "finishInit"), |
| content: { "message": dojo.json.serialize([props]) } |
| }; |
| |
| // 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"; |
| if( (urlHost != thisHost)|| |
| (urlPort != thisPort) ){ |
| dojo.debug(thisHost, urlHost); |
| dojo.debug(thisPort, urlPort); |
| |
| this._isXD = true; |
| bindArgs.transport = "ScriptSrcTransport"; |
| bindArgs.jsonParamName = "jsonp"; |
| bindArgs.method = "GET"; |
| } |
| } |
| } |
| if(bargs){ |
| dojo.lang.mixin(bindArgs, bargs); |
| } |
| return dojo.io.bind(bindArgs); |
| } |
| |
| this.finishInit = function(type, data, evt, request){ |
| data = data[0]; |
| this.handshakeReturn = data; |
| // pick a transport |
| if(data["authSuccessful"] == false){ |
| dojo.debug("cometd authentication failed"); |
| return; |
| } |
| if(data.version < this.minimumVersion){ |
| dojo.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.version = data.version; |
| this.clientId = data.clientId; |
| this.tunnelInit = dojo.lang.hitch(this.currentTransport, "tunnelInit"); |
| this.tunnelCollapse = dojo.lang.hitch(this.currentTransport, "tunnelCollapse"); |
| this.initialized = true; |
| this.currentTransport.startup(data); |
| while(this.backlog.length != 0){ |
| var cur = this.backlog.shift(); |
| var fn = cur.shift(); |
| this[fn].apply(this, cur); |
| } |
| } |
| |
| this._getRandStr = function(){ |
| return Math.random().toString().substring(2, 10); |
| } |
| |
| // public API functions called by cometd or by the transport classes |
| this.deliver = function(messages){ |
| dojo.lang.forEach(messages, this._deliver, this); |
| } |
| |
| this._deliver = function(message){ |
| // dipatch events along the specified path |
| if(!message["channel"]){ |
| dojo.debug("cometd error: no channel for message!"); |
| return; |
| } |
| if(!this.currentTransport){ |
| this.backlog.push(["deliver", message]); |
| return; |
| } |
| this.lastMessage = message; |
| // check to see if we got a /meta channel message that we care about |
| 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/subscribe": |
| if(!message.successful){ |
| dojo.debug("cometd subscription error for channel", message.channel, ":", message.error); |
| return; |
| } |
| this.subscribed(message.subscription, message); |
| break; |
| case "/meta/unsubscribe": |
| if(!message.successful){ |
| dojo.debug("cometd unsubscription error for channel", message.channel, ":", message.error); |
| return; |
| } |
| this.unsubscribed(message.subscription, message); |
| break; |
| } |
| } |
| // send the message down for processing by the transport |
| this.currentTransport.deliver(message); |
| |
| // dispatch the message to any locally subscribed listeners |
| var tname = (this.globalTopicChannels[message.channel]) ? message.channel : "/cometd"+message.channel; |
| dojo.event.topic.publish(tname, message); |
| } |
| |
| this.disconnect = function(){ |
| if(!this.currentTransport){ |
| dojo.debug("no current transport to disconnect from"); |
| return; |
| } |
| this.currentTransport.disconnect(); |
| } |
| |
| // 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 |
| if(!this.currentTransport){ |
| this.backlog.push(["publish", channel, data, properties]); |
| return; |
| } |
| var message = { |
| data: data, |
| channel: channel |
| }; |
| if(properties){ |
| dojo.lang.mixin(message, properties); |
| } |
| return this.currentTransport.sendMessage(message); |
| } |
| |
| this.subscribe = function( /*string*/ channel, |
| /*boolean, optional*/ useLocalTopics, |
| /*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 |
| // useLocalTopics: |
| // Determines if up a local event topic subscription to the passed |
| // function using the channel name that was passed is constructed, |
| // or if the topic name will be prefixed with some other |
| // identifier for local message distribution. Setting this to |
| // "true" is a good way to hook up server-sent message delivery to |
| // pre-existing local topics. |
| // 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.currentTransport){ |
| this.backlog.push(["subscribe", channel, useLocalTopics, objOrFunc, funcName]); |
| return; |
| } |
| if(objOrFunc){ |
| var tname = (useLocalTopics) ? channel : "/cometd"+channel; |
| if(useLocalTopics){ |
| this.globalTopicChannels[channel] = true; |
| } |
| dojo.event.topic.subscribe(tname, objOrFunc, funcName); |
| } |
| // FIXME: would we handle queuing of the subscription if not connected? |
| // Or should the transport object? |
| return this.currentTransport.sendMessage({ |
| channel: "/meta/subscribe", |
| subscription: channel |
| }); |
| } |
| |
| this.subscribed = function( /*string*/ channel, |
| /*obj*/ message){ |
| dojo.debug(channel); |
| dojo.debugShallow(message); |
| } |
| |
| this.unsubscribe = function(/*string*/ channel, |
| /*boolean, optional*/ useLocalTopics, |
| /*object, optional*/ objOrFunc, |
| /*string, optional*/ funcName){ // return: boolean |
| // summary: |
| // inform the server of this client's disinterest in channel |
| // channel: |
| // name of the cometd channel to subscribe to |
| // useLocalTopics: |
| // Determines if up a local event topic subscription to the passed |
| // function using the channel name that was passed is destroyed, |
| // or if the topic name will be prefixed with some other |
| // identifier for stopping message distribution. |
| // 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 |
| if(!this.currentTransport){ |
| this.backlog.push(["unsubscribe", channel, useLocalTopics, objOrFunc, funcName]); |
| return; |
| } |
| // a callback function to notifiy upon channel message delivery |
| if(objOrFunc){ |
| // FIXME: should actual local topic unsubscription be delayed for |
| // successful unsubcribe notices from the other end? (guessing "no") |
| // FIXME: if useLocalTopics is false, should we go ahead and |
| // destroy the local topic? |
| var tname = (useLocalTopics) ? channel : "/cometd"+channel; |
| dojo.event.topic.unsubscribe(tname, objOrFunc, funcName); |
| } |
| return this.currentTransport.sendMessage({ |
| channel: "/meta/unsubscribe", |
| subscription: channel |
| }); |
| } |
| |
| this.unsubscribed = function(/*string*/ channel, |
| /*obj*/ message){ |
| dojo.debug(channel); |
| dojo.debugShallow(message); |
| } |
| |
| // FIXME: add an "addPublisher" function |
| |
| } |
| |
| /* |
| transport objects MUST expose the following methods: |
| - check |
| - startup |
| - sendMessage |
| - 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.connected = false; |
| this.connectionId = null; |
| this.authToken = null; |
| this.lastTimestamp = null; |
| this.lastId = 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(this.connected){ return; } |
| // FIXME: fill in startup routine here |
| this.connected = true; |
| } |
| |
| this.sendMessage = function(message){ |
| // FIXME: fill in message sending logic |
| } |
| |
| this.deliver = function(message){ |
| if(message["timestamp"]){ |
| this.lastTimestamp = message.timestamp; |
| } |
| if(message["id"]){ |
| this.lastId = message.id; |
| } |
| 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(){ |
| if(!this.connected){ return; } |
| // FIXME: fill in shutdown routine here |
| this.connected = false; |
| } |
| } |
| cometd.connectionTypes.register("blah", cometd.blahTransport.check, cometd.blahTransport); |
| */ |
| |
| cometd.iframeTransport = new function(){ |
| this.connected = false; |
| this.connectionId = null; |
| |
| this.rcvNode = null; |
| this.rcvNodeName = ""; |
| this.phonyForm = null; |
| this.authToken = null; |
| this.lastTimestamp = null; |
| this.lastId = null; |
| this.backlog = []; |
| |
| this.check = function(types, version, xdomain){ |
| return ((!xdomain)&& |
| (!dojo.render.html.safari)&& |
| (dojo.lang.inArray(types, "iframe"))); |
| } |
| |
| this.tunnelInit = function(){ |
| // we've gotten our initialization document back in the iframe, so |
| // now open up a connection and start passing data! |
| this.postToIframe({ |
| message: dojo.json.serialize([ |
| { |
| channel: "/meta/connect", |
| clientId: cometd.clientId, |
| connectionType: "iframe" |
| // FIXME: auth not passed here! |
| // "authToken": this.authToken |
| } |
| ]) |
| }); |
| } |
| |
| this.tunnelCollapse = function(){ |
| if(this.connected){ |
| // try to restart the tunnel |
| this.connected = false; |
| |
| this.postToIframe({ |
| message: dojo.json.serialize([ |
| { |
| channel: "/meta/reconnect", |
| clientId: cometd.clientId, |
| connectionId: this.connectionId, |
| timestamp: this.lastTimestamp, |
| id: this.lastId |
| // FIXME: no authToken provision! |
| } |
| ]) |
| }); |
| } |
| } |
| |
| this.deliver = function(message){ |
| // handle delivery details that this transport particularly cares |
| // about. Most functions of should be handled by the main cometd object |
| // with only transport-specific details and state being tracked here. |
| if(message["timestamp"]){ |
| this.lastTimestamp = message.timestamp; |
| } |
| if(message["id"]){ |
| this.lastId = message.id; |
| } |
| // check to see if we got a /meta channel message that we care about |
| 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": |
| if(!message.successful){ |
| dojo.debug("cometd connection error:", message.error); |
| return; |
| } |
| this.connectionId = message.connectionId; |
| this.connected = true; |
| this.processBacklog(); |
| break; |
| case "/meta/reconnect": |
| if(!message.successful){ |
| dojo.debug("cometd reconnection error:", message.error); |
| return; |
| } |
| this.connected = true; |
| break; |
| case "/meta/subscribe": |
| if(!message.successful){ |
| dojo.debug("cometd subscription error for channel", message.channel, ":", message.error); |
| return; |
| } |
| // this.subscribed(message.channel); |
| dojo.debug(message.channel); |
| break; |
| } |
| } |
| } |
| |
| this.widenDomain = function(domainStr){ |
| // allow us to make reqests to the TLD |
| var cd = domainStr||document.domain; |
| if(cd.indexOf(".")==-1){ return; } // probably file:/// or localhost |
| var dps = cd.split("."); |
| if(dps.length<=2){ return; } // probably file:/// or an RFC 1918 address |
| dps = dps.slice(dps.length-2); |
| document.domain = dps.join("."); |
| return document.domain; |
| } |
| |
| this.postToIframe = function(content, url){ |
| if(!this.phonyForm){ |
| if(dojo.render.html.ie){ |
| this.phonyForm = document.createElement("<form enctype='application/x-www-form-urlencoded' method='POST' style='display: none;'>"); |
| dojo.body().appendChild(this.phonyForm); |
| }else{ |
| this.phonyForm = document.createElement("form"); |
| this.phonyForm.style.display = "none"; // FIXME: will this still work? |
| dojo.body().appendChild(this.phonyForm); |
| this.phonyForm.enctype = "application/x-www-form-urlencoded"; |
| this.phonyForm.method = "POST"; |
| } |
| } |
| |
| this.phonyForm.action = url||cometd.url; |
| this.phonyForm.target = this.rcvNodeName; |
| this.phonyForm.setAttribute("target", this.rcvNodeName); |
| |
| while(this.phonyForm.firstChild){ |
| this.phonyForm.removeChild(this.phonyForm.firstChild); |
| } |
| |
| for(var x in content){ |
| var tn; |
| if(dojo.render.html.ie){ |
| tn = document.createElement("<input type='hidden' name='"+x+"' value='"+content[x]+"'>"); |
| this.phonyForm.appendChild(tn); |
| }else{ |
| tn = document.createElement("input"); |
| this.phonyForm.appendChild(tn); |
| tn.type = "hidden"; |
| tn.name = x; |
| tn.value = content[x]; |
| } |
| } |
| this.phonyForm.submit(); |
| } |
| |
| this.processBacklog = function(){ |
| while(this.backlog.length > 0){ |
| this.sendMessage(this.backlog.shift(), true); |
| } |
| } |
| |
| this.sendMessage = function(message, bypassBacklog){ |
| // FIXME: what about auth fields? |
| if((bypassBacklog)||(this.connected)){ |
| message.connectionId = this.connectionId; |
| message.clientId = cometd.clientId; |
| var bindArgs = { |
| url: cometd.url||djConfig["cometdRoot"], |
| method: "POST", |
| mimetype: "text/json", |
| // FIXME: we should be able to do better than this given that we're sending an array! |
| content: { message: dojo.json.serialize([ message ]) } |
| }; |
| return dojo.io.bind(bindArgs); |
| }else{ |
| this.backlog.push(message); |
| } |
| } |
| |
| this.startup = function(handshakeData){ |
| dojo.debug("startup!"); |
| dojo.debug(dojo.json.serialize(handshakeData)); |
| |
| if(this.connected){ return; } |
| |
| // this.widenDomain(); |
| |
| // NOTE: we require the server to cooperate by hosting |
| // cometdInit.html at the designated endpoint |
| this.rcvNodeName = "cometdRcv_"+cometd._getRandStr(); |
| // the "forever frame" approach |
| |
| var initUrl = cometd.url+"/?tunnelInit=iframe"; // &domain="+document.domain; |
| if(false && dojo.render.html.ie){ // FIXME: DISALBED FOR NOW |
| // use the "htmlfile hack" to prevent the background click junk |
| this.rcvNode = new ActiveXObject("htmlfile"); |
| this.rcvNode.open(); |
| this.rcvNode.write("<html>"); |
| this.rcvNode.write("<script>document.domain = '"+document.domain+"'"); |
| this.rcvNode.write("</html>"); |
| this.rcvNode.close(); |
| |
| var ifrDiv = this.rcvNode.createElement("div"); |
| this.rcvNode.appendChild(ifrDiv); |
| this.rcvNode.parentWindow.dojo = dojo; |
| ifrDiv.innerHTML = "<iframe src='"+initUrl+"'></iframe>" |
| }else{ |
| this.rcvNode = dojo.io.createIFrame(this.rcvNodeName, "", initUrl); |
| // dojo.io.setIFrameSrc(this.rcvNode, initUrl); |
| // we're still waiting on the iframe to call back up to use and |
| // advertise that it's been initialized via tunnelInit |
| } |
| } |
| } |
| |
| cometd.mimeReplaceTransport = new function(){ |
| this.connected = false; |
| this.connectionId = null; |
| this.xhr = null; |
| |
| this.authToken = null; |
| this.lastTimestamp = null; |
| this.lastId = null; |
| this.backlog = []; |
| |
| this.check = function(types, version, xdomain){ |
| return ((!xdomain)&& |
| (dojo.render.html.mozilla)&& // seems only Moz really supports this right now = ( |
| (dojo.lang.inArray(types, "mime-message-block"))); |
| } |
| |
| this.tunnelInit = function(){ |
| if(this.connected){ return; } |
| // FIXME: open up the connection here |
| this.openTunnelWith({ |
| message: dojo.json.serialize([ |
| { |
| channel: "/meta/connect", |
| clientId: cometd.clientId, |
| connectionType: "mime-message-block" |
| // FIXME: auth not passed here! |
| // "authToken": this.authToken |
| } |
| ]) |
| }); |
| this.connected = true; |
| } |
| |
| this.tunnelCollapse = function(){ |
| if(this.connected){ |
| // try to restart the tunnel |
| this.connected = false; |
| this.openTunnelWith({ |
| message: dojo.json.serialize([ |
| { |
| channel: "/meta/reconnect", |
| clientId: cometd.clientId, |
| connectionId: this.connectionId, |
| timestamp: this.lastTimestamp, |
| id: this.lastId |
| // FIXME: no authToken provision! |
| } |
| ]) |
| }); |
| } |
| } |
| |
| this.deliver = cometd.iframeTransport.deliver; |
| // the logic appears to be the same |
| |
| this.handleOnLoad = function(resp){ |
| cometd.deliver(dojo.json.evalJson(this.xhr.responseText)); |
| } |
| |
| this.openTunnelWith = function(content, url){ |
| // set up the XHR object and register the multipart callbacks |
| this.xhr = dojo.hostenv.getXmlhttpObject(); |
| this.xhr.multipart = true; // FIXME: do Opera and Safari support this flag? |
| if(dojo.render.html.mozilla){ |
| this.xhr.addEventListener("load", dojo.lang.hitch(this, "handleOnLoad"), false); |
| }else if(dojo.render.html.safari){ |
| // Blah. WebKit doesn't actually populate responseText and/or responseXML. Useless. |
| dojo.debug("Webkit is broken with multipart responses over XHR = ("); |
| this.xhr.onreadystatechange = dojo.lang.hitch(this, "handleOnLoad"); |
| }else{ |
| this.xhr.onload = dojo.lang.hitch(this, "handleOnLoad"); |
| } |
| this.xhr.open("POST", (url||cometd.url), true); // async post |
| this.xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); |
| dojo.debug(dojo.json.serialize(content)); |
| this.xhr.send(dojo.io.argsFromMap(content, "utf8")); |
| } |
| |
| this.processBacklog = function(){ |
| while(this.backlog.length > 0){ |
| this.sendMessage(this.backlog.shift(), true); |
| } |
| } |
| |
| this.sendMessage = function(message, bypassBacklog){ |
| // FIXME: what about auth fields? |
| if((bypassBacklog)||(this.connected)){ |
| message.connectionId = this.connectionId; |
| message.clientId = cometd.clientId; |
| var bindArgs = { |
| url: cometd.url||djConfig["cometdRoot"], |
| method: "POST", |
| mimetype: "text/json", |
| content: { message: dojo.json.serialize([ message ]) } |
| }; |
| return dojo.io.bind(bindArgs); |
| }else{ |
| this.backlog.push(message); |
| } |
| } |
| |
| this.startup = function(handshakeData){ |
| dojo.debugShallow(handshakeData); |
| if(this.connected){ return; } |
| this.tunnelInit(); |
| } |
| } |
| |
| cometd.longPollTransport = new function(){ |
| this.connected = false; |
| this.connectionId = null; |
| |
| this.authToken = null; |
| this.lastTimestamp = null; |
| this.lastId = null; |
| this.backlog = []; |
| |
| this.check = function(types, version, xdomain){ |
| return ((!xdomain)&&(dojo.lang.inArray(types, "long-polling"))); |
| } |
| |
| this.tunnelInit = function(){ |
| if(this.connected){ return; } |
| // FIXME: open up the connection here |
| this.openTunnelWith({ |
| message: dojo.json.serialize([ |
| { |
| channel: "/meta/connect", |
| clientId: cometd.clientId, |
| connectionType: "long-polling" |
| // FIXME: auth not passed here! |
| // "authToken": this.authToken |
| } |
| ]) |
| }); |
| this.connected = true; |
| } |
| |
| this.tunnelCollapse = function(){ |
| if(!this.connected){ |
| // try to restart the tunnel |
| this.connected = false; |
| dojo.debug("clientId:", cometd.clientId); |
| this.openTunnelWith({ |
| message: dojo.json.serialize([ |
| { |
| channel: "/meta/reconnect", |
| connectionType: "long-polling", |
| clientId: cometd.clientId, |
| connectionId: this.connectionId, |
| timestamp: this.lastTimestamp, |
| id: this.lastId |
| // FIXME: no authToken provision! |
| } |
| ]) |
| }); |
| } |
| } |
| |
| this.deliver = cometd.iframeTransport.deliver; |
| // the logic appears to be the same |
| |
| this.openTunnelWith = function(content, url){ |
| dojo.io.bind({ |
| url: (url||cometd.url), |
| method: "post", |
| content: content, |
| mimetype: "text/json", |
| load: dojo.lang.hitch(this, function(type, data, evt, args){ |
| // dojo.debug(evt.responseText); |
| cometd.deliver(data); |
| this.connected = false; |
| this.tunnelCollapse(); |
| }), |
| error: function(){ dojo.debug("tunnel opening failed"); } |
| }); |
| this.connected = true; |
| } |
| |
| this.processBacklog = function(){ |
| while(this.backlog.length > 0){ |
| this.sendMessage(this.backlog.shift(), true); |
| } |
| } |
| |
| this.sendMessage = function(message, bypassBacklog){ |
| // FIXME: what about auth fields? |
| if((bypassBacklog)||(this.connected)){ |
| message.connectionId = this.connectionId; |
| message.clientId = cometd.clientId; |
| var bindArgs = { |
| url: cometd.url||djConfig["cometdRoot"], |
| method: "post", |
| mimetype: "text/json", |
| content: { message: dojo.json.serialize([ message ]) } |
| }; |
| return dojo.io.bind(bindArgs); |
| }else{ |
| this.backlog.push(message); |
| } |
| } |
| |
| this.startup = function(handshakeData){ |
| if(this.connected){ return; } |
| this.tunnelInit(); |
| } |
| } |
| |
| cometd.callbackPollTransport = new function(){ |
| this.connected = false; |
| this.connectionId = null; |
| |
| this.authToken = null; |
| this.lastTimestamp = null; |
| this.lastId = null; |
| this.backlog = []; |
| |
| this.check = function(types, version, xdomain){ |
| // we handle x-domain! |
| return dojo.lang.inArray(types, "callback-polling"); |
| } |
| |
| this.tunnelInit = function(){ |
| if(this.connected){ return; } |
| // FIXME: open up the connection here |
| this.openTunnelWith({ |
| message: dojo.json.serialize([ |
| { |
| channel: "/meta/connect", |
| clientId: cometd.clientId, |
| connectionType: "callback-polling" |
| // FIXME: auth not passed here! |
| // "authToken": this.authToken |
| } |
| ]) |
| }); |
| this.connected = true; |
| } |
| |
| this.tunnelCollapse = function(){ |
| if(!this.connected){ |
| // try to restart the tunnel |
| this.connected = false; |
| this.openTunnelWith({ |
| message: dojo.json.serialize([ |
| { |
| channel: "/meta/reconnect", |
| connectionType: "long-polling", |
| clientId: cometd.clientId, |
| connectionId: this.connectionId, |
| timestamp: this.lastTimestamp, |
| id: this.lastId |
| // FIXME: no authToken provision! |
| } |
| ]) |
| }); |
| } |
| } |
| |
| this.deliver = cometd.iframeTransport.deliver; |
| // the logic appears to be the same |
| |
| this.openTunnelWith = function(content, url){ |
| // create a <script> element to generate the request |
| var req = dojo.io.bind({ |
| url: (url||cometd.url), |
| content: content, |
| mimetype: "text/json", |
| transport: "ScriptSrcTransport", |
| jsonParamName: "jsonp", |
| load: dojo.lang.hitch(this, function(type, data, evt, args){ |
| dojo.debug(dojo.json.serialize(data)); |
| cometd.deliver(data); |
| this.connected = false; |
| this.tunnelCollapse(); |
| }), |
| error: function(){ dojo.debug("tunnel opening failed"); } |
| }); |
| this.connected = true; |
| } |
| |
| this.processBacklog = function(){ |
| while(this.backlog.length > 0){ |
| this.sendMessage(this.backlog.shift(), true); |
| } |
| } |
| |
| this.sendMessage = function(message, bypassBacklog){ |
| // FIXME: what about auth fields? |
| if((bypassBacklog)||(this.connected)){ |
| message.connectionId = this.connectionId; |
| message.clientId = cometd.clientId; |
| var bindArgs = { |
| url: cometd.url||djConfig["cometdRoot"], |
| mimetype: "text/json", |
| transport: "ScriptSrcTransport", |
| jsonParamName: "jsonp", |
| content: { message: dojo.json.serialize([ message ]) } |
| }; |
| return dojo.io.bind(bindArgs); |
| }else{ |
| this.backlog.push(message); |
| } |
| } |
| |
| this.startup = function(handshakeData){ |
| if(this.connected){ return; } |
| this.tunnelInit(); |
| } |
| } |
| |
| cometd.connectionTypes.register("mime-message-block", cometd.mimeReplaceTransport.check, cometd.mimeReplaceTransport); |
| cometd.connectionTypes.register("long-polling", cometd.longPollTransport.check, cometd.longPollTransport); |
| cometd.connectionTypes.register("callback-polling", cometd.callbackPollTransport.check, cometd.callbackPollTransport); |
| cometd.connectionTypes.register("iframe", cometd.iframeTransport.check, cometd.iframeTransport); |
| |
| // FIXME: need to implement fallback-polling, IE XML block |
| |
| dojo.io.cometd = cometd; |