1 |
// Set domain at highest level |
2 |
var domainparts = document.domain.split("."); |
3 |
document.domain = domainparts[domainparts.length-2]+"."+domainparts[domainparts.length-1]; |
4 |
|
5 |
Function.prototype.bind = function(obj) { |
6 |
var method = this, |
7 |
temp = function() { |
8 |
return method.apply(obj, arguments); |
9 |
}; |
10 |
return temp; |
11 |
} |
12 |
Function.prototype.andThen=function(g) { |
13 |
var f=this; |
14 |
var a=this.arguments |
15 |
return function(args) { |
16 |
f(a);g(args); |
17 |
} |
18 |
}; |
19 |
|
20 |
function Meteor(instID) { |
21 |
|
22 |
this.transferDoc = false; |
23 |
this.pingtimer = false; |
24 |
this.updatepollfreqtimer = false; |
25 |
this.lastrequest = 0; |
26 |
this.recvtimes = []; |
27 |
this.MHostId = false; |
28 |
this.callback_process = function() {}; |
29 |
this.callback_reset = function() {}; |
30 |
this.callback_eof = function() {}; |
31 |
this.callback_changemode = function() {}; |
32 |
this.callback_statuschanged = function() {}; |
33 |
this.persist = 1; |
34 |
this.frameloadtimer = false; |
35 |
this.debugmode = false; |
36 |
this.subsurl = false; |
37 |
this.channels = {}; |
38 |
this.channelcount = 0; |
39 |
this.streamreq = false; |
40 |
this.byteoffset = 0; |
41 |
|
42 |
// Documented public properties |
43 |
this.subdomain = "data"; |
44 |
this.dynamicpageaddress = "push"; |
45 |
this.smartpoll = 1; |
46 |
this.pollfreq = 2000; |
47 |
this.minpollfreq = 2000; |
48 |
this.mode = "stream"; |
49 |
this.polltimeout = 30000; |
50 |
this.pingtimeout = 10000; |
51 |
this.maxmessages = 0; |
52 |
this.status = 0; |
53 |
|
54 |
/* Statuses: 0 = Uninitialised, |
55 |
1 = Loading stream, |
56 |
2 = Loading controller frame, |
57 |
3 = Controller frame timeout, retrying. |
58 |
4 = Controller frame loaded and ready |
59 |
5 = Receiving data |
60 |
*/ |
61 |
|
62 |
this.instID = (typeof(instID) != "undefined") ? instID : 0; |
63 |
this.MHostId = Math.floor(Math.random()*100000000)+""+this.instID; |
64 |
} |
65 |
|
66 |
Meteor.instances = new Array(); |
67 |
|
68 |
Meteor.create = function(instID) { |
69 |
if (!instID) instID = Meteor.instances.length; |
70 |
Meteor.instances[instID] = new Meteor(instID); |
71 |
return Meteor.instances[instID]; |
72 |
} |
73 |
|
74 |
Meteor.register = function(ifr) { |
75 |
instid = new String(ifr.window.frameElement.id); |
76 |
instid = instid.replace(/.*_([0-9]*)$/, "$1"); |
77 |
ifr.p = this.instances[instid].process.bind(this.instances[instid]); |
78 |
ifr.r = this.instances[instid].reset.bind(this.instances[instid]); |
79 |
ifr.eof = this.instances[instid].eof.bind(this.instances[instid]); |
80 |
ifr.get = this.instances[instid].get.bind(this.instances[instid]); |
81 |
ifr.increasepolldelay = this.instances[instid].increasepolldelay.bind(this.instances[instid]); |
82 |
clearTimeout(this.instances[instid].frameloadtimer); |
83 |
this.instances[instid].setstatus(4); |
84 |
if (this.debugmode) console.log("Frame registered"); |
85 |
} |
86 |
|
87 |
Meteor.reset = function(ifr) { |
88 |
instid = new String(ifr.window.frameElement.id); |
89 |
instid = instid.replace(/.*_([0-9]*)$/, "$1"); |
90 |
this.instances[instid].reset(); |
91 |
} |
92 |
|
93 |
window.onunload = function() { |
94 |
for (var i in Meteor.instances) { |
95 |
if (Meteor.instances[i].transferDoc) delete Meteor.instances[i].transferDoc; |
96 |
} |
97 |
} |
98 |
|
99 |
|
100 |
Meteor.prototype.joinChannel = function(channelname, backtrack) { |
101 |
if (typeof(this.channels[channelname]) != "undefined") throw "Cannot join channel "+channelname+": already subscribed"; |
102 |
this.channels[channelname] = {backtrack:backtrack, lastmsgreceived:0}; |
103 |
if (this.debugmode) console.log("Joined channel "+channelname+", channel list follows"); |
104 |
if (this.debugmode) console.log(this.channels); |
105 |
if (this.status != 0) this.start(); |
106 |
this.channelcount++; |
107 |
} |
108 |
|
109 |
Meteor.prototype.leaveChannel = function(channelname) { |
110 |
if (typeof(this.channels[channelname]) == "undefined") throw "Cannot leave channel "+channelname+": not subscribed"; |
111 |
delete this.channels[channelname]; |
112 |
if (this.status != 0) this.start(); |
113 |
this.channelcount--; |
114 |
} |
115 |
|
116 |
Meteor.prototype.start = function() { |
117 |
this.persist = (this.persist)?1:0; |
118 |
this.smartpoll = (this.smartpoll)?1:0; |
119 |
this.mode = (this.mode=="stream")?"stream":"poll"; |
120 |
if (!this.subdomain || !this.channelcount) throw "Channel or Meteor subdomain host not specified"; |
121 |
this.stop(); |
122 |
var now = new Date(); |
123 |
var t = now.getTime(); |
124 |
this.setstatus(1); |
125 |
this.updateSubsUrl(); |
126 |
if (this.mode=="stream") { |
127 |
if (document.all) { |
128 |
this.createIframe(this.subsurl); |
129 |
} else { |
130 |
this.createIframe("http://"+this.subdomain+"."+location.hostname+"/stream.html"); |
131 |
} |
132 |
var f = this.pollmode.bind(this); |
133 |
clearTimeout(this.pingtimer); |
134 |
this.pingtimer = setTimeout(f, this.pingtimeout); |
135 |
|
136 |
} else { |
137 |
this.createIframe("http://"+this.subdomain+"."+location.hostname+"/poll.html?nc="+t); |
138 |
this.recvtimes[0] = t; |
139 |
if (this.updatepollfreqtimer) clearTimeout(this.updatepollfreqtimer); |
140 |
this.updatepollfreqtimer = setInterval(this.updatepollfreq.bind(this), 2500); |
141 |
} |
142 |
this.lastrequest = t; |
143 |
} |
144 |
|
145 |
Meteor.prototype.updateSubsUrl = function() { |
146 |
|
147 |
// If streaming or long polling, connection should persist |
148 |
this.persist = (this.mode == "stream" || (this.mode=='poll' && this.maxmessages > 0)) ? 1 : 0; |
149 |
var surl = "http://" + this.subdomain + "." + location.hostname + "/" + this.dynamicpageaddress + "?id=" + this.MHostId; |
150 |
if (this.persist && this.mode != "stream") surl += "&maxmessages=" + this.maxmessages; |
151 |
surl += "&persist="+this.persist; |
152 |
for (var c in this.channels) { |
153 |
surl += "&channel="+c; |
154 |
if (this.channels[c].lastmsgreceived > 0) { |
155 |
surl += "&restartfrom="+(this.channels[c].lastmsgreceived+1); |
156 |
} else if (this.channels[c].backtrack > 0) { |
157 |
surl += "&backtrack="+this.channels[c].backtrack; |
158 |
} else if (this.channels[c].backtrack < 0 || isNaN(this.channels[c].backtrack)) { |
159 |
surl += "&restartfrom="; |
160 |
} |
161 |
} |
162 |
this.subsurl = surl; |
163 |
} |
164 |
|
165 |
Meteor.prototype.createIframe = function(url) { |
166 |
delete this.transferDoc; |
167 |
if (document.all) try { this.transferDoc = new ActiveXObject("htmlfile") } catch(ex) { this.transferDoc = null } |
168 |
if (document.all && this.transferDoc) { |
169 |
this.transferDoc = new ActiveXObject("htmlfile"); |
170 |
this.transferDoc.open(); |
171 |
this.transferDoc.write("<html>"); |
172 |
this.transferDoc.write("<script>document.domain=\""+(document.domain)+"\";</"+"script>"); |
173 |
this.transferDoc.write("</html>"); |
174 |
this.transferDoc.parentWindow.Meteor = Meteor; |
175 |
this.transferDoc.close(); |
176 |
var ifrDiv = this.transferDoc.createElement("div"); |
177 |
this.transferDoc.appendChild(ifrDiv); |
178 |
ifrDiv.innerHTML = "<iframe id=\"meteorframe_"+this.instID+"\" src=\""+url+"\" style=\"display: none;\"></iframe>"; |
179 |
} else { |
180 |
var ifr = document.createElement("IFRAME"); |
181 |
ifr.style.width = "10px"; |
182 |
ifr.style.height = "10px"; |
183 |
ifr.style.border = "none"; |
184 |
ifr.style.position = "absolute"; |
185 |
ifr.style.top = "-10px"; |
186 |
ifr.style.marginTop = "-10px"; |
187 |
ifr.style.zIndex = "-20"; |
188 |
ifr.setAttribute("id", "meteorframe_"+this.instID); |
189 |
ifr.Meteor = Meteor; |
190 |
if (document.compatMode=='CSS1Compat') { |
191 |
var innerifr = document.createElement("IFRAME"); |
192 |
innerifr.setAttribute("src", url); |
193 |
innerifr.setAttribute("id", "meteorinnerframe_"+this.instID); |
194 |
ifr.appendChild(innerifr); |
195 |
document.body.appendChild(ifr); |
196 |
} else { |
197 |
ifr.setAttribute("src", url); |
198 |
document.body.appendChild(ifr); |
199 |
} |
200 |
} |
201 |
if (this.debugmode) console.log("Loading URL '"+url+"' into frame..."); |
202 |
var f = this.frameloadtimeout.bind(this); |
203 |
this.frameloadtimer = setTimeout(f, 5000); |
204 |
} |
205 |
|
206 |
Meteor.prototype.stop = function() { |
207 |
if (typeof(this.transferDoc)=="object") { |
208 |
this.transferDoc = false; |
209 |
} |
210 |
if (document.getElementById("meteorframe_"+this.instID)) { |
211 |
document.getElementById("meteorframe_"+this.instID).src="about:blank"; |
212 |
document.body.removeChild(document.getElementById("meteorframe_"+this.instID)); |
213 |
} |
214 |
clearTimeout(this.pingtimer); |
215 |
clearTimeout(this.updatepollfreqtimer); |
216 |
clearTimeout(this.frameloadtimer); |
217 |
this.setstatus(0); |
218 |
} |
219 |
|
220 |
Meteor.prototype.pollmode = function() { |
221 |
if (this.debugmode) console.log("Ping timeout"); |
222 |
this.mode="poll"; |
223 |
this.start(); |
224 |
this.callback_changemode("poll"); |
225 |
this.lastpingtime = false; |
226 |
} |
227 |
|
228 |
Meteor.prototype.process = function(id, channel, data) { |
229 |
if (id == -1) { |
230 |
if (this.debugmode) console.log("Ping"); |
231 |
this.ping(); |
232 |
} else if (typeof(this.channels[channel]) != "undefined" && id > this.channels[channel].lastmsgreceived) { |
233 |
if (this.debugmode) console.log("Message "+id+" received on channel "+channel+" (last id on channel: "+this.channels[channel].lastmsgreceived+")\n"+data); |
234 |
this.callback_process(data); |
235 |
this.channels[channel].lastmsgreceived = id; |
236 |
if (this.mode=="poll") { |
237 |
var now = new Date(); |
238 |
var t = now.getTime(); |
239 |
this.recvtimes[this.recvtimes.length] = t; |
240 |
while (this.recvtimes.length > 5) this.recvtimes.shift(); |
241 |
} |
242 |
} |
243 |
this.updateSubsUrl(); |
244 |
this.setstatus(5); |
245 |
} |
246 |
|
247 |
Meteor.prototype.ping = function() { |
248 |
if (this.mode=="stream" && this.pingtimer) { |
249 |
clearTimeout(this.pingtimer); |
250 |
var f = this.pollmode.bind(this); |
251 |
this.pingtimer = setTimeout(f, this.pingtimeout); |
252 |
var now = new Date(); |
253 |
this.lastpingtime = now.getTime(); |
254 |
} |
255 |
this.setstatus(5); |
256 |
} |
257 |
|
258 |
Meteor.prototype.reset = function() { |
259 |
if (this.debugmode) console.log("Stream reset"); |
260 |
var now = new Date(); |
261 |
var t = now.getTime(); |
262 |
var x = this.pollfreq - (t-this.lastrequest); |
263 |
if (x < 10) x = 10; |
264 |
this.ping(); |
265 |
this.callback_reset(); |
266 |
setTimeout(this.start.bind(this), x); |
267 |
} |
268 |
|
269 |
Meteor.prototype.eof = function() { |
270 |
this.callback_eof(); |
271 |
} |
272 |
|
273 |
Meteor.prototype.get = function(varname) { |
274 |
eval("var a = this."+varname+";"); |
275 |
if (typeof(a) == "undefined") throw "Cannot get value of "+varname; |
276 |
return a; |
277 |
} |
278 |
|
279 |
Meteor.prototype.increasepolldelay = function() { |
280 |
this.pollfreq *= 2; |
281 |
} |
282 |
|
283 |
Meteor.prototype.updatepollfreq = function() { |
284 |
if (this.smartpoll) { |
285 |
var now = new Date(); |
286 |
var t = now.getTime(); |
287 |
var avg = 0; |
288 |
for (var i=1; i<this.recvtimes.length; i++) { |
289 |
var x = (this.recvtimes[i]-this.recvtimes[i-1]); |
290 |
avg += (x>60000)? 60000 : x; |
291 |
} |
292 |
x = (t-this.recvtimes[this.recvtimes.length-1]); |
293 |
avg += (x>180000)? 180000 : x; |
294 |
avg /= this.recvtimes.length; |
295 |
if ((avg/3) < this.pollfreq && (avg/3) >= this.minpollfreq) this.pollfreq = Math.ceil(this.pollfreq*0.9); |
296 |
if ((avg/3) > this.pollfreq) this.pollfreq = Math.floor(this.pollfreq*1.05); |
297 |
} |
298 |
} |
299 |
|
300 |
Meteor.prototype.registerEventCallback = function(evt, funcRef) { |
301 |
if (evt=="process") { |
302 |
this.callback_process = (this.callback_process).andThen(funcRef); |
303 |
} else if (evt=="reset") { |
304 |
this.callback_reset = (this.callback_reset).andThen(funcRef); |
305 |
} else if (evt=="eof") { |
306 |
this.callback_eof = (this.callback_eof).andThen(funcRef); |
307 |
} else if (evt=="changemode") { |
308 |
this.callback_changemode = (this.callback_changemode).andThen(funcRef); |
309 |
} else if (evt=="changestatus") { |
310 |
this.callback_statuschanged = (this.callback_statuschanged).andThen(funcRef); |
311 |
} |
312 |
} |
313 |
|
314 |
Meteor.prototype.frameloadtimeout = function() { |
315 |
if (this.debugmode) console.log("Frame load timeout"); |
316 |
if (this.frameloadtimer) clearTimeout(this.frameloadtimer); |
317 |
this.setstatus(3); |
318 |
setTimeout(this.start.bind(this), 5000); |
319 |
} |
320 |
Meteor.prototype.setstatus = function(newstatus) { |
321 |
if (this.status != newstatus) { |
322 |
this.status = newstatus; |
323 |
this.callback_statuschanged(newstatus); |
324 |
} |
325 |
} |
326 |
|
327 |
|
328 |
Meteor.createCookie = function(name,value,days) { |
329 |
if (days) { |
330 |
var date = new Date(); |
331 |
date.setTime(date.getTime()+(days*24*60*60*1000)); |
332 |
var expires = "; expires="+date.toGMTString(); |
333 |
} |
334 |
else var expires = ""; |
335 |
document.cookie = name+"="+value+expires+"; path=/"; |
336 |
} |
337 |
|
338 |
Meteor.readCookie = function(name) { |
339 |
var nameEQ = name + "="; |
340 |
var ca = document.cookie.split(';'); |
341 |
for(var i=0;i < ca.length;i++) { |
342 |
var c = ca[i]; |
343 |
while (c.charAt(0)==' ') c = c.substring(1,c.length); |
344 |
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); |
345 |
} |
346 |
return null; |
347 |
} |
348 |
|
349 |
Meteor.eraseCookie = function(name) { |
350 |
createCookie(name,"",-1); |
351 |
} |