+/*\r
+stream: xhrinteractive, iframe, serversent\r
+longpoll\r
+smartpoll\r
+simplepoll\r
+*/\r
+\r
+Meteor = {\r
+\r
+ callbacks: {\r
+ process: function() {},\r
+ reset: function() {},\r
+ eof: function() {},\r
+ statuschanged: function() {},\r
+ changemode: function() {}\r
+ },\r
+ channelcount: 0,\r
+ channels: {},\r
+ debugmode: false,\r
+ frameref: null,\r
+ host: null,\r
+ hostid: null,\r
+ maxpollfreq: 60000,\r
+ minpollfreq: 2000,\r
+ mode: "stream",\r
+ pingtimeout: 20000,\r
+ pingtimer: null,\r
+ pollfreq: 3000,\r
+ port: 80,\r
+ polltimeout: 30000,\r
+ recvtimes: [],\r
+ status: 0,\r
+ updatepollfreqtimer: null,\r
+\r
+ register: function(ifr) {\r
+ ifr.p = Meteor.process;\r
+ ifr.r = Meteor.reset;\r
+ ifr.eof = Meteor.eof;\r
+ ifr.ch = Meteor.channelInfo;\r
+ clearTimeout(Meteor.frameloadtimer);\r
+ Meteor.setstatus(4);\r
+ Meteor.log("Frame registered");\r
+ },\r
+\r
+ joinChannel: function(channelname, backtrack) {\r
+ if (typeof(Meteor.channels[channelname]) != "undefined") throw "Cannot join channel "+channelname+": already subscribed";\r
+ Meteor.channels[channelname] = {backtrack:backtrack, lastmsgreceived:0};\r
+ Meteor.log("Joined channel "+channelname);\r
+ Meteor.channelcount++;\r
+ if (Meteor.status != 0) Meteor.connect();\r
+ },\r
+\r
+ leaveChannel: function(channelname) {\r
+ if (typeof(Meteor.channels[channelname]) == "undefined") throw "Cannot leave channel "+channelname+": not subscribed";\r
+ delete Meteor.channels[channelname];\r
+ Meteor.log("Left channel "+channelname);\r
+ if (Meteor.status != 0) Meteor.connect();\r
+ Meteor.channelcount--;\r
+ },\r
+\r
+ connect: function() {\r
+ Meteor.log("Connecting");\r
+ if (!Meteor.host) throw "Meteor host not specified";\r
+ if (isNaN(Meteor.port)) throw "Meteor port not specified";\r
+ if (!Meteor.channelcount) throw "No channels specified";\r
+ if (Meteor.status) Meteor.disconnect();\r
+ Meteor.setstatus(1);\r
+ var now = new Date();\r
+ var t = now.getTime();\r
+ if (!Meteor.hostid) Meteor.hostid = t+""+Math.floor(Math.random()*1000000)\r
+ document.domain = Meteor.extract_xss_domain(document.domain);\r
+ if (Meteor.mode=="stream") Meteor.mode = Meteor.selectStreamTransport();\r
+ Meteor.log("Selected "+Meteor.mode+" transport");\r
+ if (Meteor.mode=="xhrinteractive" || Meteor.mode=="iframe" || Meteor.mode=="serversent") {\r
+ if (Meteor.mode == "iframe") {\r
+ Meteor.loadFrame(Meteor.getSubsUrl());\r
+ } else {\r
+ Meteor.loadFrame("http://"+Meteor.host+((Meteor.port==80)?"":":"+Meteor.port)+"/stream.html");\r
+ }\r
+ clearTimeout(Meteor.pingtimer);\r
+ Meteor.pingtimer = setTimeout(Meteor.pollmode, Meteor.pingtimeout);\r
+\r
+ } else {\r
+ Meteor.loadFrame("http://"+Meteor.host+((Meteor.port==80)?"":":"+Meteor.port)+"/poll.html");\r
+ Meteor.recvtimes[0] = t;\r
+ if (Meteor.updatepollfreqtimer) clearTimeout(Meteor.updatepollfreqtimer);\r
+ if (Meteor.mode=='smartpoll') Meteor.updatepollfreqtimer = setInterval(Meteor.updatepollfreq, 2500);\r
+ if (Meteor.mode=='longpoll') Meteor.pollfreq = Meteor.minpollfreq;\r
+ }\r
+ Meteor.lastrequest = t;\r
+ },\r
+\r
+ disconnect: function() {\r
+ if (Meteor.status) {\r
+ clearTimeout(Meteor.pingtimer);\r
+ clearTimeout(Meteor.updatepollfreqtimer);\r
+ clearTimeout(Meteor.frameloadtimer);\r
+ if (typeof CollectGarbage == 'function') CollectGarbage();\r
+ if (Meteor.status != 6) Meteor.setstatus(0);\r
+ Meteor.log("Disconnected");\r
+ }\r
+ },\r
+ \r
+ selectStreamTransport: function() {\r
+ try {\r
+ var test = ActiveXObject;\r
+ return "iframe";\r
+ } catch (e) {}\r
+ if ((typeof window.addEventStream) == "function") return "iframe";\r
+ return "xhrinteractive";\r
+ },\r
+\r
+ getSubsUrl: function() {\r
+ var surl = "http://" + Meteor.host + ((Meteor.port==80)?"":":"+Meteor.port) + "/push/" + Meteor.hostid + "/" + Meteor.mode;\r
+ for (var c in Meteor.channels) {\r
+ surl += "/"+c;\r
+ if (Meteor.channels[c].lastmsgreceived > 0) {\r
+ surl += ".r"+(Meteor.channels[c].lastmsgreceived+1);\r
+ } else if (Meteor.channels[c].backtrack > 0) {\r
+ surl += ".b"+Meteor.channels[c].backtrack;\r
+ } else if (Meteor.channels[c].backtrack < 0 || isNaN(Meteor.channels[c].backtrack)) {\r
+ surl += ".h";\r
+ }\r
+ }\r
+ var now = new Date();\r
+ surl += "?nc="+now.getTime();\r
+ return surl;\r
+ },\r
+\r
+ loadFrame: function(url) {\r
+ try {\r
+ if (!Meteor.frameref) {\r
+ var transferDoc = new ActiveXObject("htmlfile");\r
+ Meteor.frameref = transferDoc;\r
+ }\r
+ Meteor.frameref.open();\r
+ Meteor.frameref.write("<html><script>");\r
+ Meteor.frameref.write("document.domain=\""+(document.domain)+"\";");\r
+ Meteor.frameref.write("</"+"script></html>");\r
+ Meteor.frameref.parentWindow.Meteor = Meteor;\r
+ Meteor.frameref.close();\r
+ var ifrDiv = Meteor.frameref.createElement("div");\r
+ Meteor.frameref.appendChild(ifrDiv);\r
+ ifrDiv.innerHTML = "<iframe src=\""+url+"\"></iframe>";\r
+ } catch (e) {\r
+ if (!Meteor.frameref) {\r
+ var ifr = document.createElement("IFRAME");\r
+ ifr.style.width = "10px";\r
+ ifr.style.height = "10px";\r
+ ifr.style.border = "none";\r
+ ifr.style.position = "absolute";\r
+ ifr.style.top = "-10px";\r
+ ifr.style.marginTop = "-10px";\r
+ ifr.style.zIndex = "-20";\r
+ ifr.Meteor = Meteor;\r
+ document.body.appendChild(ifr);\r
+ Meteor.frameref = ifr;\r
+ }\r
+ Meteor.frameref.setAttribute("src", url);\r
+ }\r
+ Meteor.log("Loading URL '"+url+"' into frame...");\r
+ Meteor.frameloadtimer = setTimeout(Meteor.frameloadtimeout, 5000);\r
+ },\r
+\r
+ pollmode: function() {\r
+ Meteor.log("Ping timeout");\r
+ Meteor.mode="smartpoll";\r
+ clearTimeout(Meteor.pingtimer);\r
+ Meteor.callbacks["changemode"]("poll");\r
+ Meteor.lastpingtime = false;\r
+ Meteor.connect();\r
+ },\r
+\r
+ process: function(id, channel, data) {\r
+ if (id == -1) {\r
+ Meteor.log("Ping");\r
+ Meteor.ping();\r
+ } else if (typeof(Meteor.channels[channel]) != "undefined") {\r
+ Meteor.log("Message "+id+" received on channel "+channel+" (last id on channel: "+Meteor.channels[channel].lastmsgreceived+")\n"+data);\r
+ Meteor.callbacks["process"](data);\r
+ Meteor.channels[channel].lastmsgreceived = id;\r
+ if (Meteor.mode=="smartpoll") {\r
+ var now = new Date();\r
+ Meteor.recvtimes[Meteor.recvtimes.length] = now.getTime();\r
+ while (Meteor.recvtimes.length > 5) Meteor.recvtimes.shift();\r
+ }\r
+ }\r
+ Meteor.setstatus(5);\r
+ },\r
+\r
+ ping: function() {\r
+ if (Meteor.pingtimer) {\r
+ clearTimeout(Meteor.pingtimer);\r
+ Meteor.pingtimer = setTimeout(Meteor.pollmode, Meteor.pingtimeout);\r
+ var now = new Date();\r
+ Meteor.lastpingtime = now.getTime();\r
+ }\r
+ Meteor.setstatus(5);\r
+ },\r
+\r
+ reset: function() {\r
+ if (Meteor.status != 6) {\r
+ Meteor.log("Stream reset");\r
+ Meteor.ping();\r
+ Meteor.callbacks["reset"]();\r
+ var now = new Date();\r
+ var t = now.getTime();\r
+ var x = Meteor.pollfreq - (t-Meteor.lastrequest);\r
+ if (x < 10) x = 10;\r
+ setTimeout(Meteor.connect, x);\r
+ }\r
+ },\r
+\r
+ eof: function() {\r
+ Meteor.log("Received end of stream, will not reconnect");\r
+ Meteor.callbacks["eof"]();\r
+ Meteor.setstatus(6);\r
+ Meteor.disconnect();\r
+ },\r
+\r
+ channelInfo: function(channel, id) {\r
+ Meteor.channels[channel].lastmsgreceived = id;\r
+ Meteor.log("Received channel info for channel "+channel+": resume from "+id);\r
+ },\r
+\r
+ updatepollfreq: function() {\r
+ var now = new Date();\r
+ var t = now.getTime();\r
+ var avg = 0;\r
+ for (var i=1; i<Meteor.recvtimes.length; i++) {\r
+ avg += (Meteor.recvtimes[i]-Meteor.recvtimes[i-1]);\r
+ }\r
+ avg += (t-Meteor.recvtimes[Meteor.recvtimes.length-1]);\r
+ avg /= Meteor.recvtimes.length;\r
+ var target = avg/2;\r
+ if (target < Meteor.pollfreq && Meteor.pollfreq > Meteor.minpollfreq) Meteor.pollfreq = Math.ceil(Meteor.pollfreq*0.9);\r
+ if (target > Meteor.pollfreq && Meteor.pollfreq < Meteor.maxpollfreq) Meteor.pollfreq = Math.floor(Meteor.pollfreq*1.05);\r
+ },\r
+\r
+ registerEventCallback: function(evt, funcRef) {\r
+ Function.prototype.andThen=function(g) {\r
+ var f=this;\r
+ var a=Meteor.arguments\r
+ return function(args) {\r
+ f(a);g(args);\r
+ }\r
+ };\r
+ if (typeof Meteor.callbacks[evt] == "function") {\r
+ Meteor.callbacks[evt] = (Meteor.callbacks[evt]).andThen(funcRef);\r
+ } else {\r
+ Meteor.callbacks[evt] = funcRef;\r
+ }\r
+ },\r
+\r
+ frameloadtimeout: function() {\r
+ Meteor.log("Frame load timeout");\r
+ if (Meteor.frameloadtimer) clearTimeout(Meteor.frameloadtimer);\r
+ Meteor.setstatus(3);\r
+ Meteor.pollmode();\r
+ },\r
+\r
+ extract_xss_domain: function(old_domain) {\r
+ if (old_domain.match(/^(\d{1,3}\.){3}\d{1,3}$/)) return old_domain;\r
+ domain_pieces = old_domain.split('.');\r
+ return domain_pieces.slice(-2, domain_pieces.length).join(".");\r
+ },\r
+\r
+ setstatus: function(newstatus) {\r
+ // Statuses: 0 = Uninitialised,\r
+ // 1 = Loading stream,\r
+ // 2 = Loading controller frame,\r
+ // 3 = Controller frame timeout, retrying.\r
+ // 4 = Controller frame loaded and ready\r
+ // 5 = Receiving data\r
+ // 6 = End of stream, will not reconnect\r
+\r
+ if (Meteor.status != newstatus) {\r
+ Meteor.status = newstatus;\r
+ Meteor.callbacks["statuschanged"](newstatus);\r
+ }\r
+ },\r
+\r
+ log: function(logstr) {\r
+ if (Meteor.debugmode) {\r
+ if (window.console) {\r
+ window.console.log(logstr);\r
+ } else if (document.getElementById("meteorlogoutput")) {\r
+ document.getElementById("meteorlogoutput").innerHTML += logstr+"<br/>";\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+var oldonunload = window.onunload;\r
+if (typeof window.onunload != 'function') {\r
+ window.onunload = Meteor.disconnect;\r
+} else {\r
+ window.onunload = function() {\r
+ if (oldonunload) oldonunload();\r
+ Meteor.disconnect();\r
+ }\r
+}
\ No newline at end of file