|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Callbacks count.
|
|
*/
|
|
|
|
var index = 0;
|
|
|
|
/**
|
|
* 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;
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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];
|
|
insertAt.parentNode.insertBefore(script, insertAt);
|
|
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;
|
|
}
|
|
};
|