231 lines
4.8 KiB
231 lines
4.8 KiB
|
|
/** |
|
* Module requirements. |
|
*/ |
|
|
|
var Polling = require('./polling'); |
|
var inherit = require('component-inherit'); |
|
|
|
/** |
|
* Module exports. |
|
*/ |
|
|
|
module.exports = JSONPPolling; |
|
|
|
/** |
|
* Cached regular expressions. |
|
*/ |
|
|
|
var rNewline = /\n/g; |
|
var rEscapedNewline = /\\n/g; |
|
|
|
/** |
|
* Global JSONP callbacks. |
|
*/ |
|
|
|
var callbacks; |
|
|
|
/** |
|
* Noop. |
|
*/ |
|
|
|
function empty () { } |
|
|
|
/** |
|
* JSONP Polling constructor. |
|
* |
|
* @param {Object} opts. |
|
* @api public |
|
*/ |
|
|
|
function JSONPPolling (opts) { |
|
Polling.call(this, opts); |
|
|
|
this.query = this.query || {}; |
|
|
|
// define global callbacks array if not present |
|
// we do this here (lazily) to avoid unneeded global pollution |
|
if (!callbacks) { |
|
// we need to consider multiple engines in the same page |
|
if (!global.___eio) global.___eio = []; |
|
callbacks = global.___eio; |
|
} |
|
|
|
// callback identifier |
|
this.index = callbacks.length; |
|
|
|
// add callback to jsonp global |
|
var self = this; |
|
callbacks.push(function (msg) { |
|
self.onData(msg); |
|
}); |
|
|
|
// append to query string |
|
this.query.j = this.index; |
|
|
|
// prevent spurious errors from being emitted when the window is unloaded |
|
if (global.document && global.addEventListener) { |
|
global.addEventListener('beforeunload', function () { |
|
if (self.script) self.script.onerror = empty; |
|
}, false); |
|
} |
|
} |
|
|
|
/** |
|
* Inherits from Polling. |
|
*/ |
|
|
|
inherit(JSONPPolling, Polling); |
|
|
|
/* |
|
* JSONP only supports binary as base64 encoded strings |
|
*/ |
|
|
|
JSONPPolling.prototype.supportsBinary = false; |
|
|
|
/** |
|
* Closes the socket. |
|
* |
|
* @api private |
|
*/ |
|
|
|
JSONPPolling.prototype.doClose = function () { |
|
if (this.script) { |
|
this.script.parentNode.removeChild(this.script); |
|
this.script = null; |
|
} |
|
|
|
if (this.form) { |
|
this.form.parentNode.removeChild(this.form); |
|
this.form = null; |
|
this.iframe = null; |
|
} |
|
|
|
Polling.prototype.doClose.call(this); |
|
}; |
|
|
|
/** |
|
* Starts a poll cycle. |
|
* |
|
* @api private |
|
*/ |
|
|
|
JSONPPolling.prototype.doPoll = function () { |
|
var self = this; |
|
var script = document.createElement('script'); |
|
|
|
if (this.script) { |
|
this.script.parentNode.removeChild(this.script); |
|
this.script = null; |
|
} |
|
|
|
script.async = true; |
|
script.src = this.uri(); |
|
script.onerror = function (e) { |
|
self.onError('jsonp poll error', e); |
|
}; |
|
|
|
var insertAt = document.getElementsByTagName('script')[0]; |
|
if (insertAt) { |
|
insertAt.parentNode.insertBefore(script, insertAt); |
|
} else { |
|
(document.head || document.body).appendChild(script); |
|
} |
|
this.script = script; |
|
|
|
var isUAgecko = 'undefined' !== typeof navigator && /gecko/i.test(navigator.userAgent); |
|
|
|
if (isUAgecko) { |
|
setTimeout(function () { |
|
var iframe = document.createElement('iframe'); |
|
document.body.appendChild(iframe); |
|
document.body.removeChild(iframe); |
|
}, 100); |
|
} |
|
}; |
|
|
|
/** |
|
* Writes with a hidden iframe. |
|
* |
|
* @param {String} data to send |
|
* @param {Function} called upon flush. |
|
* @api private |
|
*/ |
|
|
|
JSONPPolling.prototype.doWrite = function (data, fn) { |
|
var self = this; |
|
|
|
if (!this.form) { |
|
var form = document.createElement('form'); |
|
var area = document.createElement('textarea'); |
|
var id = this.iframeId = 'eio_iframe_' + this.index; |
|
var iframe; |
|
|
|
form.className = 'socketio'; |
|
form.style.position = 'absolute'; |
|
form.style.top = '-1000px'; |
|
form.style.left = '-1000px'; |
|
form.target = id; |
|
form.method = 'POST'; |
|
form.setAttribute('accept-charset', 'utf-8'); |
|
area.name = 'd'; |
|
form.appendChild(area); |
|
document.body.appendChild(form); |
|
|
|
this.form = form; |
|
this.area = area; |
|
} |
|
|
|
this.form.action = this.uri(); |
|
|
|
function complete () { |
|
initIframe(); |
|
fn(); |
|
} |
|
|
|
function initIframe () { |
|
if (self.iframe) { |
|
try { |
|
self.form.removeChild(self.iframe); |
|
} catch (e) { |
|
self.onError('jsonp polling iframe removal error', e); |
|
} |
|
} |
|
|
|
try { |
|
// ie6 dynamic iframes with target="" support (thanks Chris Lambacher) |
|
var html = '<iframe src="javascript:0" name="' + self.iframeId + '">'; |
|
iframe = document.createElement(html); |
|
} catch (e) { |
|
iframe = document.createElement('iframe'); |
|
iframe.name = self.iframeId; |
|
iframe.src = 'javascript:0'; |
|
} |
|
|
|
iframe.id = self.iframeId; |
|
|
|
self.form.appendChild(iframe); |
|
self.iframe = iframe; |
|
} |
|
|
|
initIframe(); |
|
|
|
// escape \n to prevent it from being converted into \r\n by some UAs |
|
// double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side |
|
data = data.replace(rEscapedNewline, '\\\n'); |
|
this.area.value = data.replace(rNewline, '\\n'); |
|
|
|
try { |
|
this.form.submit(); |
|
} catch (e) {} |
|
|
|
if (this.iframe.attachEvent) { |
|
this.iframe.onreadystatechange = function () { |
|
if (self.iframe.readyState === 'complete') { |
|
complete(); |
|
} |
|
}; |
|
} else { |
|
this.iframe.onload = complete; |
|
} |
|
};
|
|
|