polling-jsonp.js (4896B)
1 /** 2 * Module requirements. 3 */ 4 5 var Polling = require('./polling'); 6 var inherit = require('component-inherit'); 7 var globalThis = require('../globalThis'); 8 9 /** 10 * Module exports. 11 */ 12 13 module.exports = JSONPPolling; 14 15 /** 16 * Cached regular expressions. 17 */ 18 19 var rNewline = /\n/g; 20 var rEscapedNewline = /\\n/g; 21 22 /** 23 * Global JSONP callbacks. 24 */ 25 26 var callbacks; 27 28 /** 29 * Noop. 30 */ 31 32 function empty () { } 33 34 /** 35 * JSONP Polling constructor. 36 * 37 * @param {Object} opts. 38 * @api public 39 */ 40 41 function JSONPPolling (opts) { 42 Polling.call(this, opts); 43 44 this.query = this.query || {}; 45 46 // define global callbacks array if not present 47 // we do this here (lazily) to avoid unneeded global pollution 48 if (!callbacks) { 49 // we need to consider multiple engines in the same page 50 callbacks = globalThis.___eio = (globalThis.___eio || []); 51 } 52 53 // callback identifier 54 this.index = callbacks.length; 55 56 // add callback to jsonp global 57 var self = this; 58 callbacks.push(function (msg) { 59 self.onData(msg); 60 }); 61 62 // append to query string 63 this.query.j = this.index; 64 65 // prevent spurious errors from being emitted when the window is unloaded 66 if (typeof addEventListener === 'function') { 67 addEventListener('beforeunload', function () { 68 if (self.script) self.script.onerror = empty; 69 }, false); 70 } 71 } 72 73 /** 74 * Inherits from Polling. 75 */ 76 77 inherit(JSONPPolling, Polling); 78 79 /* 80 * JSONP only supports binary as base64 encoded strings 81 */ 82 83 JSONPPolling.prototype.supportsBinary = false; 84 85 /** 86 * Closes the socket. 87 * 88 * @api private 89 */ 90 91 JSONPPolling.prototype.doClose = function () { 92 if (this.script) { 93 this.script.parentNode.removeChild(this.script); 94 this.script = null; 95 } 96 97 if (this.form) { 98 this.form.parentNode.removeChild(this.form); 99 this.form = null; 100 this.iframe = null; 101 } 102 103 Polling.prototype.doClose.call(this); 104 }; 105 106 /** 107 * Starts a poll cycle. 108 * 109 * @api private 110 */ 111 112 JSONPPolling.prototype.doPoll = function () { 113 var self = this; 114 var script = document.createElement('script'); 115 116 if (this.script) { 117 this.script.parentNode.removeChild(this.script); 118 this.script = null; 119 } 120 121 script.async = true; 122 script.src = this.uri(); 123 script.onerror = function (e) { 124 self.onError('jsonp poll error', e); 125 }; 126 127 var insertAt = document.getElementsByTagName('script')[0]; 128 if (insertAt) { 129 insertAt.parentNode.insertBefore(script, insertAt); 130 } else { 131 (document.head || document.body).appendChild(script); 132 } 133 this.script = script; 134 135 var isUAgecko = 'undefined' !== typeof navigator && /gecko/i.test(navigator.userAgent); 136 137 if (isUAgecko) { 138 setTimeout(function () { 139 var iframe = document.createElement('iframe'); 140 document.body.appendChild(iframe); 141 document.body.removeChild(iframe); 142 }, 100); 143 } 144 }; 145 146 /** 147 * Writes with a hidden iframe. 148 * 149 * @param {String} data to send 150 * @param {Function} called upon flush. 151 * @api private 152 */ 153 154 JSONPPolling.prototype.doWrite = function (data, fn) { 155 var self = this; 156 157 if (!this.form) { 158 var form = document.createElement('form'); 159 var area = document.createElement('textarea'); 160 var id = this.iframeId = 'eio_iframe_' + this.index; 161 var iframe; 162 163 form.className = 'socketio'; 164 form.style.position = 'absolute'; 165 form.style.top = '-1000px'; 166 form.style.left = '-1000px'; 167 form.target = id; 168 form.method = 'POST'; 169 form.setAttribute('accept-charset', 'utf-8'); 170 area.name = 'd'; 171 form.appendChild(area); 172 document.body.appendChild(form); 173 174 this.form = form; 175 this.area = area; 176 } 177 178 this.form.action = this.uri(); 179 180 function complete () { 181 initIframe(); 182 fn(); 183 } 184 185 function initIframe () { 186 if (self.iframe) { 187 try { 188 self.form.removeChild(self.iframe); 189 } catch (e) { 190 self.onError('jsonp polling iframe removal error', e); 191 } 192 } 193 194 try { 195 // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) 196 var html = '<iframe src="javascript:0" name="' + self.iframeId + '">'; 197 iframe = document.createElement(html); 198 } catch (e) { 199 iframe = document.createElement('iframe'); 200 iframe.name = self.iframeId; 201 iframe.src = 'javascript:0'; 202 } 203 204 iframe.id = self.iframeId; 205 206 self.form.appendChild(iframe); 207 self.iframe = iframe; 208 } 209 210 initIframe(); 211 212 // escape \n to prevent it from being converted into \r\n by some UAs 213 // double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side 214 data = data.replace(rEscapedNewline, '\\\n'); 215 this.area.value = data.replace(rNewline, '\\n'); 216 217 try { 218 this.form.submit(); 219 } catch (e) {} 220 221 if (this.iframe.attachEvent) { 222 this.iframe.onreadystatechange = function () { 223 if (self.iframe.readyState === 'complete') { 224 complete(); 225 } 226 }; 227 } else { 228 this.iframe.onload = complete; 229 } 230 };