| |
| /** |
| * 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; |
| } |
| }; |