--- googlecode.com/svn/trunk/public_html/meteor.js 2006/11/20 17:59:30 3 +++ googlecode.com/svn/trunk/public_html/meteor.js 2008/03/25 22:34:16 60 @@ -1,279 +1,312 @@ -// Set domain at highest level -var domainparts = document.domain.split("."); -document.domain = domainparts[domainparts.length-2]+"."+domainparts[domainparts.length-1]; - -Function.prototype.bind = function(obj) { - var method = this, - temp = function() { - return method.apply(obj, arguments); - }; - return temp; -} -Function.prototype.andThen=function(g) { - var f=this; - var a=this.arguments - return function(args) { - f(a);g(args); - } -}; - -function Meteor(instID) { - - this.lastmsgreceived = -1; - this.transferDoc = false; - this.pingtimer = false; - this.updatepollfreqtimer = false; - this.lastrequest = 0; - this.recvtimes = new Array(); - this.MHostId = false; - this.callback_process = function() {}; - this.callback_reset = function() {}; - this.callback_eof = function() {}; - this.callback_changemode = function() {}; - this.persist = true; - - // Documented public properties - this.channel = false; - this.subdomain = "data"; - this.dynamicpageaddress = "push"; - this.backtrack = 0; - this.smartpoll = true; - this.pollfreq = 2000; - this.minpollfreq = 2000; - this.mode = "stream"; - this.polltimeout=30000; - this.maxmessages=0; - this.pingtimeout = 10000; - - // Set or retrieve host id. Cookie takes this form: - // MeteorID=123:6356353/124:098320454; - var MeteIds = Meteor.readCookie("MeteorID"); - var regex1 = new RegExp("^([0-9\:\/M]+\/)*"+instID+"\:([^\/]+)(\/[0-9\:\/M]+)*$"); - var regex2 = new RegExp("^([0-9\:\/M]+\/)*M\:([^\/]+)(\/[0-9\:\/M]+)*$"); - if (typeof(instID) == "Number" && regex1.exec(MeteIds)) { - this.MHostId = ma[2]; - } else if (typeof(instID) == "Number") { - this.MHostId = Math.floor(Math.random()*1000000); - var newcookie = (MeteIds)?MeteIds+"/":""; - newcookie += instID+":"+this.MHostId; - Meteor.createCookie("MeteorID", newcookie); - } else if (ma = regex2.exec(MeteIds)) { - this.MHostId = ma[2]; - } else { - this.MHostId = Math.floor(Math.random()*1000000); - var newcookie = (MeteIds)?MeteIds+"/":""; - newcookie += "M:"+this.MHostId; - Meteor.createCookie("MeteorID", newcookie); - } - this.instID = (typeof(instID) != "undefined") ? instID : 0; -} - -Meteor.instances = new Array(); -Meteor.servertimeoffset = 0; - -Meteor.create = function(instID) { - if (!instID) instID = 0; - Meteor.instances[instID] = new Meteor(instID); - return Meteor.instances[instID]; -} +/* +stream: xhrinteractive, iframe, serversent +longpoll +smartpoll +simplepoll +*/ + +Meteor = { + + callbacks: { + process: function() {}, + reset: function() {}, + eof: function() {}, + statuschanged: function() {}, + changemode: function() {} + }, + channelcount: 0, + channels: {}, + debugmode: false, + frameref: null, + host: null, + hostid: null, + maxpollfreq: 60000, + minpollfreq: 2000, + mode: "stream", + pingtimeout: 20000, + pingtimer: null, + pollfreq: 3000, + port: 80, + polltimeout: 30000, + recvtimes: [], + status: 0, + updatepollfreqtimer: null, + + register: function(ifr) { + ifr.p = Meteor.process; + ifr.r = Meteor.reset; + ifr.eof = Meteor.eof; + ifr.ch = Meteor.channelInfo; + clearTimeout(Meteor.frameloadtimer); + Meteor.setstatus(4); + Meteor.log("Frame registered"); + }, + + joinChannel: function(channelname, backtrack) { + if (typeof(Meteor.channels[channelname]) != "undefined") throw "Cannot join channel "+channelname+": already subscribed"; + Meteor.channels[channelname] = {backtrack:backtrack}; + Meteor.log("Joined channel "+channelname); + Meteor.channelcount++; + if (Meteor.status != 0) Meteor.connect(); + }, + + leaveChannel: function(channelname) { + if (typeof(Meteor.channels[channelname]) == "undefined") throw "Cannot leave channel "+channelname+": not subscribed"; + delete Meteor.channels[channelname]; + Meteor.log("Left channel "+channelname); + Meteor.channelcount--; + if (Meteor.channelcount && Meteor.status != 0) Meteor.connect(); + else Meteor.disconnect(); + }, + + connect: function() { + Meteor.log("Connecting"); + if (!Meteor.host) throw "Meteor host not specified"; + if (isNaN(Meteor.port)) throw "Meteor port not specified"; + if (!Meteor.channelcount) throw "No channels specified"; + if (Meteor.status) Meteor.disconnect(); + Meteor.setstatus(1); + var now = new Date(); + var t = now.getTime(); + if (!Meteor.hostid) Meteor.hostid = t+""+Math.floor(Math.random()*1000000) + document.domain = Meteor.extract_xss_domain(document.domain); + if (Meteor.mode=="stream") Meteor.mode = Meteor.selectStreamTransport(); + Meteor.log("Selected "+Meteor.mode+" transport"); + if (Meteor.mode=="xhrinteractive" || Meteor.mode=="iframe" || Meteor.mode=="serversent") { + if (Meteor.mode == "iframe") { + Meteor.loadFrame(Meteor.getSubsUrl()); + } else { + Meteor.loadFrame("http://"+Meteor.host+((Meteor.port==80)?"":":"+Meteor.port)+"/stream.html"); + } + clearTimeout(Meteor.pingtimer); + Meteor.pingtimer = setTimeout(Meteor.pollmode, Meteor.pingtimeout); -Meteor.register = function(ifr) { - instid = new String(ifr.window.frameElement.id); - instid = instid.replace("meteorframe_", ""); - ifr.p = this.instances[instid].process.bind(this.instances[instid]); - ifr.r = this.instances[instid].reset.bind(this.instances[instid]); - ifr.eof = this.instances[instid].eof.bind(this.instances[instid]); - ifr.get = this.instances[instid].get.bind(this.instances[instid]); - ifr.increasepolldelay = this.instances[instid].increasepolldelay.bind(this.instances[instid]); -} + } else { + Meteor.loadFrame("http://"+Meteor.host+((Meteor.port==80)?"":":"+Meteor.port)+"/poll.html"); + Meteor.recvtimes[0] = t; + if (Meteor.updatepollfreqtimer) clearTimeout(Meteor.updatepollfreqtimer); + if (Meteor.mode=='smartpoll') Meteor.updatepollfreqtimer = setInterval(Meteor.updatepollfreq, 10000); + if (Meteor.mode=='longpoll') Meteor.pollfreq = Meteor.minpollfreq; + } + Meteor.lastrequest = t; + }, -Meteor.setServerTime = function(timestamp) { - var now = new Date(); - var clienttime = (now.getTime() / 1000); - Meteor.servertimeoffset = timestamp - clienttime; -} + disconnect: function() { + if (Meteor.status) { + clearTimeout(Meteor.pingtimer); + clearTimeout(Meteor.updatepollfreqtimer); + clearTimeout(Meteor.frameloadtimer); + if (typeof CollectGarbage == 'function') CollectGarbage(); + if (Meteor.status != 6) Meteor.setstatus(0); + if (Meteor.frameref.tagName=='IFRAME') { + Meteor.frameref.parentNode.removeChild(Meteor.frameref); + } else { + Meteor.frameref.open(); + Meteor.frameref.close(); + } + delete Meteor.frameref; + Meteor.log("Disconnected"); + } + }, + + selectStreamTransport: function() { + try { + var test = ActiveXObject; + return "iframe"; + } catch (e) {} + if ((typeof window.addEventStream) == "function") return "iframe"; + return "xhrinteractive"; + }, + + getSubsUrl: function() { + var surl = "http://" + Meteor.host + ((Meteor.port==80)?"":":"+Meteor.port) + "/push/" + Meteor.hostid + "/" + Meteor.mode; + for (var c in Meteor.channels) { + surl += "/"+c; + if (typeof Meteor.channels[c].lastmsgreceived != 'undefined' && Meteor.channels[c].lastmsgreceived >= 0) { + surl += ".r"+(Meteor.channels[c].lastmsgreceived+1); + } else if (Meteor.channels[c].backtrack > 0) { + surl += ".b"+Meteor.channels[c].backtrack; + } else if (Meteor.channels[c].backtrack != undefined) { + surl += ".h"; + } + } + var now = new Date(); + surl += "?nc="+now.getTime(); + return surl; + }, + + loadFrame: function(url) { + try { + if (!Meteor.frameref) { + var transferDoc = new ActiveXObject("htmlfile"); + Meteor.frameref = transferDoc; + } + Meteor.frameref.open(); + Meteor.frameref.write("