From 98e2821b38a775737e42a2479a6bc65107210859 Mon Sep 17 00:00:00 2001 From: Elliot Kroo Date: Thu, 11 Mar 2010 15:21:30 -0800 Subject: reorganizing the first level of folders (trunk/branch folders are not the git way :) --- .../net.appjet.ajstdlib/streaming-client.js | 920 +++++++++++++++++++++ 1 file changed, 920 insertions(+) create mode 100644 infrastructure/net.appjet.ajstdlib/streaming-client.js (limited to 'infrastructure/net.appjet.ajstdlib/streaming-client.js') diff --git a/infrastructure/net.appjet.ajstdlib/streaming-client.js b/infrastructure/net.appjet.ajstdlib/streaming-client.js new file mode 100644 index 0000000..3bfa227 --- /dev/null +++ b/infrastructure/net.appjet.ajstdlib/streaming-client.js @@ -0,0 +1,920 @@ +/** + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var host = window.location.host; + +function WebSocket(id) { + var self = this; + var socket = this; + var version = 2; + + var timeouts = {}; + + this.onopen = function() { } + this.onclosed = function() { } + this.onmessage = function() { } + this.onhiccup = function() { } + this.onlogmessage = function() { } + this.CONNECTING = 0; + this.OPEN = 1; + this.CLOSED = 2; + this.readyState = -1; + + var hiccupsLastMinute = 0; + var hiccupResetInterval = setInterval(function() { + hiccupsLastMinute = 0; + if (self.readyState == self.CLOSED) + clearInterval(hiccupResetInterval); + }, 60*1000); + + var isHiccuping = false; + function hiccup(channel) { + if (channel != officialChannel && channel != self) return; + if (isHiccuping) return; + log("hiccup: "+channel.name); + if (hiccupsLastMinute++ > 10) { + doDisconnect({reconnect: true, reason: "Too many hiccups!"}); + return; + } + closeAllChannels(); + timeout(timeouts, "hiccup", 15000, function() { + isHiccuping = false; + doDisconnect({reconnect: false, reason: "Couldn't contact server to hiccup."}); + }); + isHiccuping = true; + function tryHiccup() { + if (! isHiccuping) return; + self.onhiccup({connected: false}); + log("trying hiccup"); + timeout(timeouts, "singleHiccup", 5000, function() { + tryHiccup(); + }); + simpleXhr('post', postPath(), true, [{key: "oob", value: "hiccup"}], function(sc, msg) { + if (! isHiccuping) return; + if (msg.substring(0, "restart-fail".length) == "restart-fail") { + doDisconnect({reconnect: true, reason: "Server restarted or socket timed out on server."}); + } else if (sc != 200 || msg.substring(0, 2) != "ok") { + log("Failed to hiccup with error: "+sc+" / "+msg); + setTimeout(tryHiccup, 500); + } else { + isHiccuping = false; + timeouts.singleHiccup(); + timeouts.hiccup(); + doConnect(); + } + }); + } + tryHiccup(); + } + function closeAllChannels() { + for (var i in activeChannels) { + if (activeChannels.hasOwnProperty(i)) { + activeChannels[i].disconnect(); + } + } + officialChannel = undefined; + } + + function doDisconnect(obj, silent, sync) { + log("disconnected: "+obj.reason+" / "+(obj.data !== undefined ? "data: "+obj.data : "")); + logAll(); + closeAllChannels(); + if (longPollingIFrame && longPollingIFrame.div) { + longPollingIFrame.div.innerHTML = ""; + } + if (self.readyState != self.CLOSED) { + self.readyState = self.CLOSED; + if (! silent) { + postSingleMessageNow(true, "kill:"+obj.reason, sync, true); + } + self.onclosed(obj); + } + } + + this.disconnect = function(sync) { + doDisconnect({reason: "Closed by client."}, false, sync); + } + + + function doBasicConnect() { + var type = getBasicChannel(); + log("basic connect on type: "+type); + var channel = activeChannels[type] = new channelConstructors[type](); + channel.connect(); + } + + function doOtherConnect() { + var channels = getOtherChannels(); + var channel; var type; + for (var i = 0; i < channels.length; ++i) { + type = channels[i]; + log("other connect on type: "+type); + channel = activeChannels[type] = new channelConstructors[type](); + channel.connect(); + } + } + function doConnect() { + log("doing connect!"); + timeout(timeouts, "connect", 15000, function() { + doDisconnect({reconnect: false, reason: "Timeout connecting to server: no channel type was able to connect."}); + }); + doBasicConnect(); + } + + this.connect = function() { + log("socket connecting: "+id); + doConnect(); + } + + // util + function nicetime() { return Math.floor((new Date()).valueOf() / 100) % 10000000; } + function log(s) { self.onlogmessage("(comet @t: "+nicetime()+") "+s); } + function logAll() { + log(self.describe()) + } + this.describe = function() { + function describeChannels() { + out = []; + for (var i in activeChannels) { + if (activeChannels.hasOwnProperty(i)) { + out.push(i+": "+activeChannels[i].describe()); + } + } + return "[ "+out.join(", ")+" ]"; + } + return ("socket state: { id: "+id+", readyState: "+self.readyState+", isHiccuping: "+isHiccuping+", timeouts: "+describeTimeouts(timeouts)+", officialChannel: "+(officialChannel?officialChannel.name:"none")+", channels: "+describeChannels()+", isPosting: "+isPosting+", lastReceivedSeqNumber: "+lastReceivedSeqNumber+", lastPost: "+lastPost+", postTimeouts: "+describeTimeouts(postTimeouts)+", channelSeq: "+channelSeq+" }"); + } + + function wrapMethod(obj, method) { + return function() { + var arr = []; + for (var i=0; i < arguments.length; i++) { + arr.push(arguments[i]); + } + method.apply(obj, arr); + } + } + var _wm = wrapMethod; + + // cb should take statusCode, responseText, and optionally request + function simpleXhr(method, uri, async, params, cb, makeXhr) { +// log("making simple Xhr: "+[method, uri, async, params].join(", ")); + var request = (makeXhr || newRequestObject)(); + request.open(method, uri, async); + if (async) { + request.onreadystatechange = function() { + if (request.readyState != 4) return; + var status; + var responseText; + try { + status = request.status; + responseText = request.responseText; + } catch (e) { /* absorb ff error accessing request properties */ } + cb(status, responseText, request); + } + } + var data = null; + if (params) { + data = []; + for (var i = 0; i < params.length; ++i) { + data.push(encodeURIComponent(params[i].key)+"="+encodeURIComponent(params[i].value)); + } + data = data.join("&"); + request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); + } + try { + request.send(data); + } catch (e) { request.abort(); cb(500, "Error sending data!", request); } + if (! async) { + var status; + var responseText; + try { + status = request.status; + responseText = request.responseText; + } catch (e) { /* absorb ff error accessing request properties */ } + cb(status, responseText, request); + } + return request; + } + + var timeout_noop = function() { } + function timeout(timeoutObject, timeoutName, millis, timeoutCallback) { + function clearIt(timeoutObject, timeoutName) { + if (timeoutObject[timeoutName]) { + timeoutObject[timeoutName](); + timeoutObject[timeoutName] = timeout_noop; + } + } + var timeoutId = setTimeout(function() { clearIt(timeoutObject, timeoutName); timeoutCallback(); }, millis); + var f = function() { + clearTimeout(timeoutId); + } + clearIt(timeoutObject, timeoutName); + timeoutObject[timeoutName] = f; + return f; + } + + // handling messages + var lastReceivedSeqNumber = 0; + + function dataHandler(msg) { + if (msg.seqNumber > lastReceivedSeqNumber+1) { + log("bad sequence number. expecting: "+(lastReceivedSeqNumber+1)+", got: "+msg.seqNumber); + hiccup(self); + return false; + } + if (msg.seqNumber < lastReceivedSeqNumber+1) return true; + lastReceivedSeqNumber = msg.seqNumber; + if (! msg.isControl) { + self.onmessage({ data: msg.content }); + return true; + } else { + if (msg.content == "kill") { + doDisconnect({reconnect: false, reason: "Killed by server."}); + return false; + } + } + } + + // client-server comm + var postPath = function() { + return "%contextPath%/post?r="+randomVar()+"&v="+version+"&id="+id+"&seq="+lastReceivedSeqNumber; + } + + function SimpleQueue() { + var base = []; + var head = 0; + var tail = 0; + this.offer = function(data) { + base[tail++] = data; + } + this.poll = function() { + if (this.length() > 0) { + var n = base[head]; + delete base[head++]; + return n; + } + } + this.clear = function() { + head = 0; + tail = 0; + var oldBase = base; + base = []; + return oldBase; + } + this.length = function() { + return tail - head; + } + } + var outgoingMessageQueue = new SimpleQueue(); + var isPosting = false; + var postTimeouts = {}; + var lastPost; + + function postSingleMessageNow(isControl, data, sync, force, cb) { + doPostMessages([{oob: isControl, data: data, cb: cb}], sync, force) + } + + function doPostMessages(messages, sync, force, cb) { + if (! force && self.readyState == self.CLOSED) return; + if (messages.length == 0) { + if (cb) cb(); + return; + } + var data = []; + var callbacks = []; + for (var i = 0; i < messages.length; ++i) { + data.push({key: (messages[i].oob ? "oob" : "m"), + value: messages[i].data}); + if (messages[i].cb) + callbacks.push(messages[i].cb); + } + function postFailed(sc, msg, req, src) { + var str = ""; + try { + str = sc + ": "+req.statusText+" - "+msg+" ("+src+")"; + } catch (e) { /* absorb potential Firefox error accessing req */ } + doDisconnect({reconnect: true, reason: "Posting message failed.", data: str}); + for (var i = 0; i < callbacks.length; ++i) { + callbacks[i](false, str); + } + } + function postCallback(sc, msg, request) { + postTimeouts.post(); + if (sc != 200 || msg.substring(0, 2) != "ok") { + postFailed(sc, msg, request, "1"); + } else { + for (var i = 0; i < callbacks.length; ++i) { + callbacks[i](true); + } + if (cb) cb(); + } + } + timeout(postTimeouts, "post", 15000, function() { + doDisconnect({reconnect: true, reason: "Posting message timed out."}); + }); + simpleXhr('post', postPath(), ! sync, data, postCallback); + } + + function postPendingMessages() { + if (isPosting == true) + return; + var messages = outgoingMessageQueue.clear(); + if (messages.length == 0) { + return; + } + isPosting = true; + doPostMessages(messages, false, false, function() { isPosting = false; setTimeout(postPendingMessages, 0); }); + lastPost = nicetime(); + } + this.postMessage = function(data, cb) { + if (self.readyState != self.OPEN) { + return; + } + outgoingMessageQueue.offer({data: data, cb: cb}); + setTimeout(function() { postPendingMessages() }, 0); + } + + // transports + function getValidChannels() { + var channels = []; + for (var i = 0; i < validChannels.length; ++i) { + var type = validChannels[i]; + if (window.location.hash.length > 0) { + if (window.location.hash != "#"+type) { + continue; + } + } + if ($ && $.browser.opera && type != 'shortpolling' && type != 'streaming') { + continue; + } + channels.push(type); + } + return channels; + } + function getBasicChannel() { + return getValidChannels()[0]; + } + + function getOtherChannels() { + return getValidChannels().slice(1); + } + + var officialChannel; + this.getTransportType = function() { + return (officialChannel ? officialChannel.name : "none"); + } + var validChannels = "%acceptableChannelTypes%"; + var canUseSubdomains = "%canUseSubdomains%"; + var activeChannels = {}; + var channelConstructors = { + shortpolling: ShortPollingChannel, + longpolling: LongPollingChannel, + streaming: StreamingChannel + } + + function describeTimeouts(timeouts) { + var out = []; + for (var i in timeouts) { + if (timeouts.hasOwnProperty(i)) { + out.push(i+": "+(timeouts[i] == timeout_noop ? "unset" : "set")); + } + } + return "{ "+out.join(", ")+" }"; + } + + var channelSeq = 1; + function notifyConnect(channel) { + timeouts.connect(); + if (! officialChannel || channel.weight > officialChannel.weight) { + log("switching to use channel: "+channel.name); + var oldChannel = officialChannel; + officialChannel = channel; + setTimeout(function() { + postSingleMessageNow(true, "useChannel:"+(channelSeq++)+":"+channel.name, false, false, function(success, msg) { + if (success) { + if (oldChannel) { + oldChannel.disconnect(); + } else { + // there was no old channel, so try connecting the other channels. + doOtherConnect(); + } + if (self.readyState != self.OPEN) { + self.readyState = self.OPEN; + self.onopen({}); + } else { + self.onhiccup({connected: true}); + } + } else { + doDisconnect({reconnect: true, reason: "Failed to select channel on server.", data: msg}); + } + }); + }, 0); + return true; + } else { + return false; + } + } + + function randomVar() { + return String(Math.round(Math.random()*1e12)); + } + + function channelPath() { + return "%contextPath%/channel?v="+version+"&r="+randomVar()+"&id="+id; + } + + function newRequestObject() { + var xmlhttp=false; + try { + xmlhttp = (window.ActiveXObject && new ActiveXObject("Msxml2.XMLHTTP")) + } catch (e) { + try { + xmlhttp = (window.ActiveXObject && new ActiveXObject("Microsoft.XMLHTTP")); + } catch (E) { + xmlhttp = false; + } + } + if (!xmlhttp && typeof XMLHttpRequest!='undefined') { + try { + xmlhttp = new XMLHttpRequest(); + } catch (e) { + xmlhttp=false; + } + } + if (!xmlhttp && window.createRequest) { + try { + xmlhttp = window.createRequest(); + } catch (e) { + xmlhttp=false; + } + } + return xmlhttp + } + + function DataFormatError(message) { + this.message = message; + } + + function readMessage(data, startIndex) { + if (! startIndex) startIndex = 0; + var sep = data.indexOf(":", startIndex); + if (sep < 0) return; // don't have all the bytes for this yet. + var chars = Number(data.substring(startIndex, sep)); + if (isNaN(chars)) + throw new DataFormatError("Bad length: "+data.substring(startIndex, sep)); + if (data.length < sep+1+chars) return; // don't have all the bytes for this yet. + var msg = data.substr(sep+1, chars); + return { message: msg, lastConsumedChar: sep+1+chars } + } + + function iframeReader(data, startIndex) { + if (startIndex == 0) + return { message: data, lastConsumedChar: data.length } + } + + function parseWireFormat(data, startIndex, reader) { + if (! startIndex) startIndex = 0; + var msgs = []; + var readThroughIndex = startIndex; + while (true) { + var msgObj = (reader || readMessage)(data, readThroughIndex) + if (! msgObj) break; + readThroughIndex = msgObj.lastConsumedChar; + var msg = msgObj.message; + var split = msg.split(":"); + if (split[0] == 'oob') { + msgs.push({oob: split.slice(1).join(":")}); + continue; + } + var seq = Number(split[0]); + if (isNaN(seq)) + throw new DataFormatError("Bad sequence number: "+split[0]); + var control = Number(split[1]); + if (isNaN(control)) + throw new DataFormatError("Bad control: "+split[1]); + var msgContent = split.slice(2).join(":"); + msgs.push({seqNumber: seq, isControl: (control == 1), content: msgContent}); + } + return { messages: msgs, lastConsumedChar: readThroughIndex } + } + + function handleMessages(data, cursor, channel, reader) { + try { + messages = parseWireFormat(data, cursor, reader); + } catch (e) { + if (e instanceof DataFormatError) { + log("Data format error: "+e.message); + hiccup(channel); + return; + } else { + log(e.toString()+" on line: "+e.lineNumber); + } + } + for (var i=0; i < messages.messages.length; i++) { + var oob = messages.messages[i].oob; + if (oob) { + if (oob == "restart-fail") { + doDisconnect({reconnect: true, reason: "Server restarted or socket timed out on server."}); + return; + } + } else { + if (! dataHandler(messages.messages[i])) + break; + } + } + return messages.lastConsumedChar; + } + + function ShortPollingChannel() { + this.weight = 0; + this.name = "shortpolling"; + + this.isConnected = false; + this.isClosed = false; + this.request; + this.clearRequest = function() { + if (this.request) { + this.request.abort(); + this.request = null; + } + } + this.timeouts = {}; + + this.describe = function() { + return "{ isConnected: "+this.isConnected+", isClosed: "+this.isClosed+", timeouts: "+describeTimeouts(this.timeouts)+", request: "+(this.request?"set":"not set")+" }" + } + + this.pollDataHandler = function(sc, response, request) { + if (request.readyState != 4) return; + if (this.timeouts.poll) this.timeouts.poll(); + var messages; + if (! this.isConnected) { + this.timeouts.connectAttempt(); + if (sc != 200) { + log(this.name+" connect failed: "+sc+" / "+response); + setTimeout(_wm(this, this.attemptConnect), 500); + return; + } + var msg = (response ? readMessage(response) : undefined); + if (msg && msg.message == "oob:ok") { + this.timeouts.initialConnect(); + this.isConnected = true; + log(this.name+" transport connected!"); + if (! notifyConnect(this)) { + // there are better options connected. + log(this.name+" transport not chosen for activation."); + this.disconnect(); + return; + } + this.doPoll(); + return; + } else { + log(this.name+" connect didn't get ok: "+sc+" / "+response); + setTimeout(_wm(this, this.attemptConnect), 500); + return; + } + } + var chars = handleMessages(request.responseText, 0, this); + if (sc != 200 || ((! chars) && this.emptyResponseBad)) { + hiccup(this); + } + setTimeout(_wm(this, this.doPoll), this.pollDelay); + this.clearRequest(); + } + + this.keepRetryingConnection = true; + this.cancelConnect = function() { + this.clearRequest(); + this.keepRetryingConnection = false; + } + this.cancelPoll = function() { + this.clearRequest(); + log("poll timed out."); + hiccup(this); + } + + this.doPoll = function() { + if (this.isClosed) return; + timeout(this.timeouts, "poll", this.pollTimeout, _wm(this, this.cancelPoll)); + this.request = + simpleXhr('GET', + channelPath()+"&channel="+this.name+"&seq="+lastReceivedSeqNumber+this.pollParams(), + true, undefined, _wm(this, this.pollDataHandler), this.xhrGenerator); + } + + this.pollParams = function() { + return ""; + } + this.pollTimeout = 5000; + this.pollDelay = 500; + + this.attemptConnect = function() { + if (! this.keepRetryingConnection) return; + log(this.name+" attempting connect"); + this.clearRequest(); + timeout(this.timeouts, "connectAttempt", 5000, _wm(this, this.attemptConnect)); + this.request = simpleXhr('GET', channelPath()+"&channel="+this.name+"&new=yes&create="+(socket.readyState == socket.OPEN ? "no" : "yes")+"&seq="+lastReceivedSeqNumber, + true, undefined, _wm(this, this.pollDataHandler), this.xhrGenerator); + } + this.connect = function() { + this.attemptConnect(); + timeout(this.timeouts, "initialConnect", 15000, _wm(this, this.cancelConnect)); + } + this.disconnect = function() { + log(this.name+" disconnected"); + this.isClosed = true; + this.clearRequest(); + } + } + + function StreamingChannel() { + this.weight = 2; + this.name = "streaming"; + var self = this; + + var isConnected = false; + var request; + function clearRequest() { + if (request) { + request.abort(); + request = null; + if (theStream) theStream = null; + if (ifrDiv) { + ifrDiv.innerHTML = ""; + ifrDiv = null; + } + } + } + var isClosed = false; + var timeouts = {}; + var cursor = 0; + + this.describe = function() { + return "{ isConnected: "+isConnected+", isClosed: "+isClosed+", timeouts: "+describeTimeouts(timeouts)+", request: "+(request?"set":"not set")+", cursor: "+cursor+" }"; + }; + + function connectOk() { + isConnected = true; + timeouts.initialConnect(); + if (! notifyConnect(self)) { + log("streaming transport not chosen for activation"); + self.disconnect(); + return; + } + } + + function streamDataHandler() { + if (timeouts.data) timeouts.data(); + if (isClosed) return; + try { + if (! request.responseText) return; + } catch (e) { return; } + if (! isConnected) { + var msg = readMessage(request.responseText, cursor); + if (! msg) return; + cursor = msg.lastReceivedSeqNumber; + if (msg.message == "oob:ok") { + connectOk(); + } else { + log("stream: incorrect channel connect message:"+msg.message); + self.disconnect(); + return; + } + } else { + cursor = handleMessages(request.responseText, cursor, self); + } + if (! request || request.readyState == 4) { + clearRequest(); + if (isConnected) { + log("stream connection unexpectedly closed."); + hiccup(self); + return; + } + } + timeout(timeouts, "data", 60*1000, function() { hiccup(self); }); + } + + function iframeDataHandler(data) { + if (isClosed) return; + if (! isConnected) { + if (data == "oob:ok") { + connectOk(); + } else { + log("iframe stream: unexpected data on connect - "+data); + } + } else { + handleMessages(data, 0, self, iframeReader); + } + } + + function cancelConnect() { + isClosed = true; + clearRequest(); + log("stream: failed to connect."); + } + + // IE Stuff. + var theStream; + var ifrDiv; + var iframeTestCount = 0; + function testIframe() { + var state; + try { + state = ifrDiv.firstChild.readyState; + } catch (e) { + hiccup(self); + return; + } + if (state == 'interactive' || iframeTestCount > 10) { + try { var tmp = ifrDiv.firstChild.contentWindow.document.getElementById("thebody") } + catch (e) { hiccup(self); } + } else { + iframeTestCount++; + setTimeout(testIframe, 500); + } + } + + this.connect = function() { + timeout(timeouts, "initialConnect", 15000, cancelConnect) + + if (canUseSubdomains) { + var streamurl = "//"+randomVar()+".comet."+host+channelPath()+"&channel=streaming&type=iframe&new=yes&create="+(socket.readyState == socket.OPEN ? "no" : "yes")+"&seq="+lastReceivedSeqNumber; + log("stream to: "+streamurl); + if ($ && $.browser.opera) { + // set up the opera stream; requires jquery because, why not? + ifrDiv = $('
').get(0); + $('body').append(ifrDiv); + window.comet = { + pass_data: iframeDataHandler, + disconnect: function() { hiccup(self); } + } + $(ifrDiv).append($("")); + iframeTestCount = 0; + setTimeout(testIframe, 2000); + // if event-source supported disconnect notifications, fuck yeah we'd use it. +// theStream = $(''); +// var streamurl = channelPath()+"&channel=streaming&type=opera&new=yes&create="+(socket.readyState == socket.OPEN ? "no" : "yes")+"&seq="+lastReceivedSeqNumber; +// theStream.get(0).addEventListener('message', function(event) { +// iframeDataHandler(event.data); +// }, false); +// theStream.attr('src', streamurl); + log("stream connect sent!"); + return; + } + try { // TODO: remove reference to both theStream and ifrDiv on unload! + theStream = (window.ActiveXObject && new ActiveXObject("htmlfile")); + if (theStream) { + theStream.open(); + theStream.write("f<\/title><\/head><body>") + theStream.write("<s"+"cript>document.domain='"+document.domain+"';<\/s"+"cript>") + theStream.write("<\/body><\/html>") + theStream.close(); + ifrDiv = theStream.createElement("div") + theStream.appendChild(ifrDiv) + theStream.parentWindow.comet = { + pass_data: iframeDataHandler, + disconnect: function() { hiccup(self); } + } + ifrDiv.innerHTML = "<iframe src='"+streamurl+"'></iframe>"; + iframeTestCount = 0; + setTimeout(testIframe, 2000); + } + } catch (e) { + theStream = false + } + } else if ($ && $.browser.opera) { + // opera thinks it can do a normal stream, but it can't. + log("opera - not trying xhr"); + return; + } + // End IE Stuff. + if (! theStream) { + request = newRequestObject(); + request.open('get', channelPath()+"&channel=streaming&new=yes&create="+(socket.readyState == socket.OPEN ? "no" : "yes")+"&seq="+lastReceivedSeqNumber); + request.onreadystatechange = streamDataHandler; + try { + request.send(null); + } catch (e) { } + } + log("stream connect sent!"); + } + + this.disconnect = function() { + log("stream disconnected"); + isClosed = true; + clearRequest(); + } + log("new streamchannel"); + } + + // long-polling related stuff. + function iframePath(key) { + return "//"+key+".comet."+host+"%contextPath%/xhrXdFrame"; + } + + function createHiddenDiv() { + if (! document.getElementById('newcomethidden')) { + var d = document.createElement('div'); + d.setAttribute('id', 'newcomethidden'); + d.style.display = 'none'; + document.body.appendChild(d); + } + return document.getElementById('newcomethidden'); + } + + function ExtHostXHR(iframe) { + this.open = function(method, uri, async) { + this.method = method; + this.uri = uri; + this.async = async; + } + var headers = {}; + this.setRequestHeader = function(name, value) { + headers[name] = value; + } + this.send = function(data) { + var self = this; + this.xhr = iframe.iframe.contentWindow.doAction(this.method, this.uri, this.async, headers, data || null, function(status, response) { + self.readyState = 4; + self.status = status; + self.responseText = response; + self.onreadystatechange(); + }); + } + this.abort = function() { + if (this.xhr) + iframe.contentWindow.doAbort(this.xhr); + } + } + + function createRequestIframe(cb) { + var randomKey = randomVar(); + try { + var activeXControl = (window.ActiveXObject && new ActiveXObject("htmlfile")); + var htmlfileDiv; + if (activeXControl) { + activeXControl.open(); + activeXControl.write('<html><head><title>f'); + activeXControl.write('document.domain=\''+document.domain+'\';'); + activeXControl.write(''); + activeXControl.close(); + htmlfileDiv = activeXControl.createElement('div'); + activeXControl.appendChild(htmlfileDiv); + activeXControl.parentWindow["done_"+randomKey] = cb; + htmlfileDiv.innerHTML = ""; + return {iframe: htmlfileDiv.firstChild /* should be an iframe */, axc: activeXControl, div: htmlfileDiv}; + } + } catch (e) { + activeXControl = false; + } + log("Not using IE setup."); + var requestIframe = document.createElement('iframe'); + createHiddenDiv().appendChild(requestIframe); + window["done_"+randomKey] = function() { try { delete window["done_"+randomKey]; } catch (e) { }; cb(); } + requestIframe.src = iframePath(randomKey); + return {iframe: requestIframe}; + } + + function createIframeRequestObject() { + if (! longPollingIFrame) throw Error("WebSocket isn't properly set up!"); + return new ExtHostXHR(longPollingIFrame); + } + + var longPollingIFrame; + function LongPollingChannel() { + ShortPollingChannel.apply(this); // sets up other state. + this.weight = 1; + this.name = "longpolling"; + + this.pollDelay = 0; + this.pollTimeout = 15000; + this.pollParams = function() { + return "&timeout="+(this.pollTimeout-5000); + } + var connect = this.connect; + this.connect = function() { + if (! longPollingIFrame) { + longPollingIFrame = + createRequestIframe(_wm(this, connect)); // specifically *not* this.connect. we want the old one! + } else { + connect.apply(this); + } + } + this.xhrGenerator = createIframeRequestObject; + this.emptyResponseBad = true; + } +} \ No newline at end of file -- cgit v1.2.3