1 |
andrew.betts |
32 |
/* |
2 |
|
|
stream: xhrinteractive, iframe, serversent |
3 |
|
|
longpoll |
4 |
|
|
smartpoll |
5 |
|
|
simplepoll |
6 |
|
|
*/ |
7 |
|
|
|
8 |
|
|
Meteor = { |
9 |
|
|
|
10 |
|
|
callbacks: { |
11 |
|
|
process: function() {}, |
12 |
|
|
reset: function() {}, |
13 |
|
|
eof: function() {}, |
14 |
|
|
statuschanged: function() {}, |
15 |
|
|
changemode: function() {} |
16 |
|
|
}, |
17 |
|
|
channelcount: 0, |
18 |
|
|
channels: {}, |
19 |
|
|
debugmode: false, |
20 |
|
|
frameref: null, |
21 |
|
|
host: null, |
22 |
|
|
hostid: null, |
23 |
|
|
maxpollfreq: 60000, |
24 |
|
|
minpollfreq: 2000, |
25 |
|
|
mode: "stream", |
26 |
|
|
pingtimeout: 20000, |
27 |
|
|
pingtimer: null, |
28 |
andrew.betts |
39 |
pollfreq: 3000, |
29 |
andrew.betts |
32 |
port: 80, |
30 |
|
|
polltimeout: 30000, |
31 |
|
|
recvtimes: [], |
32 |
|
|
status: 0, |
33 |
|
|
updatepollfreqtimer: null, |
34 |
|
|
|
35 |
|
|
register: function(ifr) { |
36 |
|
|
ifr.p = Meteor.process; |
37 |
|
|
ifr.r = Meteor.reset; |
38 |
|
|
ifr.eof = Meteor.eof; |
39 |
andrew.betts |
53 |
ifr.ch = Meteor.channelInfo; |
40 |
andrew.betts |
32 |
clearTimeout(Meteor.frameloadtimer); |
41 |
|
|
Meteor.setstatus(4); |
42 |
|
|
Meteor.log("Frame registered"); |
43 |
|
|
}, |
44 |
|
|
|
45 |
|
|
joinChannel: function(channelname, backtrack) { |
46 |
|
|
if (typeof(Meteor.channels[channelname]) != "undefined") throw "Cannot join channel "+channelname+": already subscribed"; |
47 |
andrew.betts |
59 |
Meteor.channels[channelname] = {backtrack:backtrack}; |
48 |
andrew.betts |
32 |
Meteor.log("Joined channel "+channelname); |
49 |
|
|
Meteor.channelcount++; |
50 |
andrew.betts |
61 |
if (Meteor.status != 0 && Meteor.status != 6) Meteor.connect(); |
51 |
andrew.betts |
32 |
}, |
52 |
|
|
|
53 |
|
|
leaveChannel: function(channelname) { |
54 |
|
|
if (typeof(Meteor.channels[channelname]) == "undefined") throw "Cannot leave channel "+channelname+": not subscribed"; |
55 |
|
|
delete Meteor.channels[channelname]; |
56 |
|
|
Meteor.log("Left channel "+channelname); |
57 |
|
|
Meteor.channelcount--; |
58 |
andrew.betts |
61 |
if (Meteor.channelcount && Meteor.status != 0 && Meteor.status != 6) Meteor.connect(); |
59 |
andrew.betts |
59 |
else Meteor.disconnect(); |
60 |
andrew.betts |
32 |
}, |
61 |
|
|
|
62 |
|
|
connect: function() { |
63 |
|
|
Meteor.log("Connecting"); |
64 |
|
|
if (!Meteor.host) throw "Meteor host not specified"; |
65 |
|
|
if (isNaN(Meteor.port)) throw "Meteor port not specified"; |
66 |
|
|
if (!Meteor.channelcount) throw "No channels specified"; |
67 |
|
|
if (Meteor.status) Meteor.disconnect(); |
68 |
|
|
Meteor.setstatus(1); |
69 |
|
|
var now = new Date(); |
70 |
|
|
var t = now.getTime(); |
71 |
|
|
if (!Meteor.hostid) Meteor.hostid = t+""+Math.floor(Math.random()*1000000) |
72 |
|
|
document.domain = Meteor.extract_xss_domain(document.domain); |
73 |
|
|
if (Meteor.mode=="stream") Meteor.mode = Meteor.selectStreamTransport(); |
74 |
|
|
Meteor.log("Selected "+Meteor.mode+" transport"); |
75 |
|
|
if (Meteor.mode=="xhrinteractive" || Meteor.mode=="iframe" || Meteor.mode=="serversent") { |
76 |
|
|
if (Meteor.mode == "iframe") { |
77 |
|
|
Meteor.loadFrame(Meteor.getSubsUrl()); |
78 |
|
|
} else { |
79 |
|
|
Meteor.loadFrame("http://"+Meteor.host+((Meteor.port==80)?"":":"+Meteor.port)+"/stream.html"); |
80 |
|
|
} |
81 |
|
|
clearTimeout(Meteor.pingtimer); |
82 |
|
|
Meteor.pingtimer = setTimeout(Meteor.pollmode, Meteor.pingtimeout); |
83 |
|
|
|
84 |
|
|
} else { |
85 |
|
|
Meteor.loadFrame("http://"+Meteor.host+((Meteor.port==80)?"":":"+Meteor.port)+"/poll.html"); |
86 |
|
|
Meteor.recvtimes[0] = t; |
87 |
|
|
if (Meteor.updatepollfreqtimer) clearTimeout(Meteor.updatepollfreqtimer); |
88 |
andrew.betts |
60 |
if (Meteor.mode=='smartpoll') Meteor.updatepollfreqtimer = setInterval(Meteor.updatepollfreq, 10000); |
89 |
andrew.betts |
32 |
if (Meteor.mode=='longpoll') Meteor.pollfreq = Meteor.minpollfreq; |
90 |
|
|
} |
91 |
|
|
Meteor.lastrequest = t; |
92 |
|
|
}, |
93 |
|
|
|
94 |
|
|
disconnect: function() { |
95 |
|
|
if (Meteor.status) { |
96 |
|
|
clearTimeout(Meteor.pingtimer); |
97 |
|
|
clearTimeout(Meteor.updatepollfreqtimer); |
98 |
|
|
clearTimeout(Meteor.frameloadtimer); |
99 |
|
|
if (typeof CollectGarbage == 'function') CollectGarbage(); |
100 |
andrew.betts |
55 |
if (Meteor.status != 6) Meteor.setstatus(0); |
101 |
|
|
Meteor.log("Disconnected"); |
102 |
andrew.betts |
61 |
try { Meteor.frameref.parentNode.removeChild(Meteor.frameref); delete Meteor.frameref; return true; } catch(e) { } |
103 |
|
|
try { Meteor.frameref.open(); Meteor.frameref.close(); return true; } catch(e) {} |
104 |
andrew.betts |
32 |
} |
105 |
|
|
}, |
106 |
|
|
|
107 |
|
|
selectStreamTransport: function() { |
108 |
|
|
try { |
109 |
|
|
var test = ActiveXObject; |
110 |
|
|
return "iframe"; |
111 |
|
|
} catch (e) {} |
112 |
|
|
if ((typeof window.addEventStream) == "function") return "iframe"; |
113 |
|
|
return "xhrinteractive"; |
114 |
|
|
}, |
115 |
|
|
|
116 |
|
|
getSubsUrl: function() { |
117 |
|
|
var surl = "http://" + Meteor.host + ((Meteor.port==80)?"":":"+Meteor.port) + "/push/" + Meteor.hostid + "/" + Meteor.mode; |
118 |
|
|
for (var c in Meteor.channels) { |
119 |
|
|
surl += "/"+c; |
120 |
andrew.betts |
59 |
if (typeof Meteor.channels[c].lastmsgreceived != 'undefined' && Meteor.channels[c].lastmsgreceived >= 0) { |
121 |
andrew.betts |
32 |
surl += ".r"+(Meteor.channels[c].lastmsgreceived+1); |
122 |
|
|
} else if (Meteor.channels[c].backtrack > 0) { |
123 |
|
|
surl += ".b"+Meteor.channels[c].backtrack; |
124 |
andrew.betts |
59 |
} else if (Meteor.channels[c].backtrack != undefined) { |
125 |
andrew.betts |
32 |
surl += ".h"; |
126 |
|
|
} |
127 |
|
|
} |
128 |
andrew.betts |
55 |
var now = new Date(); |
129 |
|
|
surl += "?nc="+now.getTime(); |
130 |
andrew.betts |
32 |
return surl; |
131 |
|
|
}, |
132 |
|
|
|
133 |
|
|
loadFrame: function(url) { |
134 |
|
|
try { |
135 |
andrew.betts |
39 |
if (!Meteor.frameref) { |
136 |
|
|
var transferDoc = new ActiveXObject("htmlfile"); |
137 |
|
|
Meteor.frameref = transferDoc; |
138 |
|
|
} |
139 |
|
|
Meteor.frameref.open(); |
140 |
|
|
Meteor.frameref.write("<html><script>"); |
141 |
|
|
Meteor.frameref.write("document.domain=\""+(document.domain)+"\";"); |
142 |
|
|
Meteor.frameref.write("</"+"script></html>"); |
143 |
|
|
Meteor.frameref.parentWindow.Meteor = Meteor; |
144 |
|
|
Meteor.frameref.close(); |
145 |
|
|
var ifrDiv = Meteor.frameref.createElement("div"); |
146 |
|
|
Meteor.frameref.appendChild(ifrDiv); |
147 |
andrew.betts |
32 |
ifrDiv.innerHTML = "<iframe src=\""+url+"\"></iframe>"; |
148 |
|
|
} catch (e) { |
149 |
andrew.betts |
39 |
if (!Meteor.frameref) { |
150 |
|
|
var ifr = document.createElement("IFRAME"); |
151 |
|
|
ifr.style.width = "10px"; |
152 |
|
|
ifr.style.height = "10px"; |
153 |
|
|
ifr.style.border = "none"; |
154 |
|
|
ifr.style.position = "absolute"; |
155 |
|
|
ifr.style.top = "-10px"; |
156 |
|
|
ifr.style.marginTop = "-10px"; |
157 |
|
|
ifr.style.zIndex = "-20"; |
158 |
|
|
ifr.Meteor = Meteor; |
159 |
|
|
document.body.appendChild(ifr); |
160 |
|
|
Meteor.frameref = ifr; |
161 |
|
|
} |
162 |
|
|
Meteor.frameref.setAttribute("src", url); |
163 |
andrew.betts |
32 |
} |
164 |
|
|
Meteor.log("Loading URL '"+url+"' into frame..."); |
165 |
|
|
Meteor.frameloadtimer = setTimeout(Meteor.frameloadtimeout, 5000); |
166 |
|
|
}, |
167 |
|
|
|
168 |
|
|
pollmode: function() { |
169 |
|
|
Meteor.log("Ping timeout"); |
170 |
andrew.betts |
60 |
if (Meteor.mode != "smartpoll") { |
171 |
|
|
Meteor.mode="smartpoll"; |
172 |
|
|
Meteor.callbacks["changemode"]("poll"); |
173 |
|
|
clearTimeout(Meteor.pingtimer); |
174 |
|
|
Meteor.lastpingtime = false; |
175 |
|
|
} |
176 |
andrew.betts |
53 |
Meteor.connect(); |
177 |
andrew.betts |
32 |
}, |
178 |
|
|
|
179 |
|
|
process: function(id, channel, data) { |
180 |
|
|
if (id == -1) { |
181 |
|
|
Meteor.log("Ping"); |
182 |
|
|
Meteor.ping(); |
183 |
andrew.betts |
53 |
} else if (typeof(Meteor.channels[channel]) != "undefined") { |
184 |
andrew.betts |
32 |
Meteor.log("Message "+id+" received on channel "+channel+" (last id on channel: "+Meteor.channels[channel].lastmsgreceived+")\n"+data); |
185 |
|
|
Meteor.callbacks["process"](data); |
186 |
|
|
Meteor.channels[channel].lastmsgreceived = id; |
187 |
|
|
if (Meteor.mode=="smartpoll") { |
188 |
|
|
var now = new Date(); |
189 |
|
|
Meteor.recvtimes[Meteor.recvtimes.length] = now.getTime(); |
190 |
|
|
while (Meteor.recvtimes.length > 5) Meteor.recvtimes.shift(); |
191 |
|
|
} |
192 |
|
|
} |
193 |
|
|
Meteor.setstatus(5); |
194 |
|
|
}, |
195 |
|
|
|
196 |
|
|
ping: function() { |
197 |
|
|
if (Meteor.pingtimer) { |
198 |
|
|
clearTimeout(Meteor.pingtimer); |
199 |
|
|
Meteor.pingtimer = setTimeout(Meteor.pollmode, Meteor.pingtimeout); |
200 |
|
|
var now = new Date(); |
201 |
|
|
Meteor.lastpingtime = now.getTime(); |
202 |
|
|
} |
203 |
|
|
Meteor.setstatus(5); |
204 |
|
|
}, |
205 |
|
|
|
206 |
|
|
reset: function() { |
207 |
andrew.betts |
61 |
if (Meteor.status != 6 && Meteor.status != 0) { |
208 |
andrew.betts |
55 |
Meteor.log("Stream reset"); |
209 |
|
|
Meteor.ping(); |
210 |
|
|
Meteor.callbacks["reset"](); |
211 |
|
|
var now = new Date(); |
212 |
|
|
var t = now.getTime(); |
213 |
|
|
var x = Meteor.pollfreq - (t-Meteor.lastrequest); |
214 |
|
|
if (x < 10) x = 10; |
215 |
|
|
setTimeout(Meteor.connect, x); |
216 |
|
|
} |
217 |
andrew.betts |
32 |
}, |
218 |
|
|
|
219 |
|
|
eof: function() { |
220 |
andrew.betts |
55 |
Meteor.log("Received end of stream, will not reconnect"); |
221 |
andrew.betts |
32 |
Meteor.callbacks["eof"](); |
222 |
andrew.betts |
55 |
Meteor.setstatus(6); |
223 |
andrew.betts |
53 |
Meteor.disconnect(); |
224 |
andrew.betts |
32 |
}, |
225 |
|
|
|
226 |
andrew.betts |
53 |
channelInfo: function(channel, id) { |
227 |
|
|
Meteor.channels[channel].lastmsgreceived = id; |
228 |
|
|
Meteor.log("Received channel info for channel "+channel+": resume from "+id); |
229 |
|
|
}, |
230 |
|
|
|
231 |
andrew.betts |
32 |
updatepollfreq: function() { |
232 |
|
|
var now = new Date(); |
233 |
|
|
var t = now.getTime(); |
234 |
|
|
var avg = 0; |
235 |
|
|
for (var i=1; i<Meteor.recvtimes.length; i++) { |
236 |
|
|
avg += (Meteor.recvtimes[i]-Meteor.recvtimes[i-1]); |
237 |
|
|
} |
238 |
|
|
avg += (t-Meteor.recvtimes[Meteor.recvtimes.length-1]); |
239 |
|
|
avg /= Meteor.recvtimes.length; |
240 |
|
|
var target = avg/2; |
241 |
|
|
if (target < Meteor.pollfreq && Meteor.pollfreq > Meteor.minpollfreq) Meteor.pollfreq = Math.ceil(Meteor.pollfreq*0.9); |
242 |
|
|
if (target > Meteor.pollfreq && Meteor.pollfreq < Meteor.maxpollfreq) Meteor.pollfreq = Math.floor(Meteor.pollfreq*1.05); |
243 |
|
|
}, |
244 |
|
|
|
245 |
|
|
registerEventCallback: function(evt, funcRef) { |
246 |
|
|
Function.prototype.andThen=function(g) { |
247 |
|
|
var f=this; |
248 |
|
|
var a=Meteor.arguments |
249 |
|
|
return function(args) { |
250 |
|
|
f(a);g(args); |
251 |
|
|
} |
252 |
|
|
}; |
253 |
|
|
if (typeof Meteor.callbacks[evt] == "function") { |
254 |
|
|
Meteor.callbacks[evt] = (Meteor.callbacks[evt]).andThen(funcRef); |
255 |
|
|
} else { |
256 |
|
|
Meteor.callbacks[evt] = funcRef; |
257 |
|
|
} |
258 |
|
|
}, |
259 |
|
|
|
260 |
|
|
frameloadtimeout: function() { |
261 |
|
|
Meteor.log("Frame load timeout"); |
262 |
|
|
if (Meteor.frameloadtimer) clearTimeout(Meteor.frameloadtimer); |
263 |
|
|
Meteor.setstatus(3); |
264 |
andrew.betts |
53 |
Meteor.pollmode(); |
265 |
andrew.betts |
32 |
}, |
266 |
|
|
|
267 |
|
|
extract_xss_domain: function(old_domain) { |
268 |
|
|
if (old_domain.match(/^(\d{1,3}\.){3}\d{1,3}$/)) return old_domain; |
269 |
|
|
domain_pieces = old_domain.split('.'); |
270 |
|
|
return domain_pieces.slice(-2, domain_pieces.length).join("."); |
271 |
|
|
}, |
272 |
|
|
|
273 |
|
|
setstatus: function(newstatus) { |
274 |
|
|
// Statuses: 0 = Uninitialised, |
275 |
|
|
// 1 = Loading stream, |
276 |
|
|
// 2 = Loading controller frame, |
277 |
|
|
// 3 = Controller frame timeout, retrying. |
278 |
|
|
// 4 = Controller frame loaded and ready |
279 |
|
|
// 5 = Receiving data |
280 |
andrew.betts |
55 |
// 6 = End of stream, will not reconnect |
281 |
andrew.betts |
32 |
|
282 |
|
|
if (Meteor.status != newstatus) { |
283 |
|
|
Meteor.status = newstatus; |
284 |
|
|
Meteor.callbacks["statuschanged"](newstatus); |
285 |
|
|
} |
286 |
|
|
}, |
287 |
|
|
|
288 |
|
|
log: function(logstr) { |
289 |
|
|
if (Meteor.debugmode) { |
290 |
|
|
if (window.console) { |
291 |
|
|
window.console.log(logstr); |
292 |
|
|
} else if (document.getElementById("meteorlogoutput")) { |
293 |
|
|
document.getElementById("meteorlogoutput").innerHTML += logstr+"<br/>"; |
294 |
|
|
} |
295 |
|
|
} |
296 |
|
|
} |
297 |
|
|
} |
298 |
|
|
|
299 |
|
|
var oldonunload = window.onunload; |
300 |
|
|
if (typeof window.onunload != 'function') { |
301 |
|
|
window.onunload = Meteor.disconnect; |
302 |
|
|
} else { |
303 |
|
|
window.onunload = function() { |
304 |
|
|
if (oldonunload) oldonunload(); |
305 |
|
|
Meteor.disconnect(); |
306 |
|
|
} |
307 |
|
|
} |