polling.js (4947B)
1 /** 2 * Module dependencies. 3 */ 4 5 var Transport = require('../transport'); 6 var parseqs = require('parseqs'); 7 var parser = require('engine.io-parser'); 8 var inherit = require('component-inherit'); 9 var yeast = require('yeast'); 10 var debug = require('debug')('engine.io-client:polling'); 11 12 /** 13 * Module exports. 14 */ 15 16 module.exports = Polling; 17 18 /** 19 * Is XHR2 supported? 20 */ 21 22 var hasXHR2 = (function () { 23 var XMLHttpRequest = require('xmlhttprequest-ssl'); 24 var xhr = new XMLHttpRequest({ xdomain: false }); 25 return null != xhr.responseType; 26 })(); 27 28 /** 29 * Polling interface. 30 * 31 * @param {Object} opts 32 * @api private 33 */ 34 35 function Polling (opts) { 36 var forceBase64 = (opts && opts.forceBase64); 37 if (!hasXHR2 || forceBase64) { 38 this.supportsBinary = false; 39 } 40 Transport.call(this, opts); 41 } 42 43 /** 44 * Inherits from Transport. 45 */ 46 47 inherit(Polling, Transport); 48 49 /** 50 * Transport name. 51 */ 52 53 Polling.prototype.name = 'polling'; 54 55 /** 56 * Opens the socket (triggers polling). We write a PING message to determine 57 * when the transport is open. 58 * 59 * @api private 60 */ 61 62 Polling.prototype.doOpen = function () { 63 this.poll(); 64 }; 65 66 /** 67 * Pauses polling. 68 * 69 * @param {Function} callback upon buffers are flushed and transport is paused 70 * @api private 71 */ 72 73 Polling.prototype.pause = function (onPause) { 74 var self = this; 75 76 this.readyState = 'pausing'; 77 78 function pause () { 79 debug('paused'); 80 self.readyState = 'paused'; 81 onPause(); 82 } 83 84 if (this.polling || !this.writable) { 85 var total = 0; 86 87 if (this.polling) { 88 debug('we are currently polling - waiting to pause'); 89 total++; 90 this.once('pollComplete', function () { 91 debug('pre-pause polling complete'); 92 --total || pause(); 93 }); 94 } 95 96 if (!this.writable) { 97 debug('we are currently writing - waiting to pause'); 98 total++; 99 this.once('drain', function () { 100 debug('pre-pause writing complete'); 101 --total || pause(); 102 }); 103 } 104 } else { 105 pause(); 106 } 107 }; 108 109 /** 110 * Starts polling cycle. 111 * 112 * @api public 113 */ 114 115 Polling.prototype.poll = function () { 116 debug('polling'); 117 this.polling = true; 118 this.doPoll(); 119 this.emit('poll'); 120 }; 121 122 /** 123 * Overloads onData to detect payloads. 124 * 125 * @api private 126 */ 127 128 Polling.prototype.onData = function (data) { 129 var self = this; 130 debug('polling got data %s', data); 131 var callback = function (packet, index, total) { 132 // if its the first message we consider the transport open 133 if ('opening' === self.readyState) { 134 self.onOpen(); 135 } 136 137 // if its a close packet, we close the ongoing requests 138 if ('close' === packet.type) { 139 self.onClose(); 140 return false; 141 } 142 143 // otherwise bypass onData and handle the message 144 self.onPacket(packet); 145 }; 146 147 // decode payload 148 parser.decodePayload(data, this.socket.binaryType, callback); 149 150 // if an event did not trigger closing 151 if ('closed' !== this.readyState) { 152 // if we got data we're not polling 153 this.polling = false; 154 this.emit('pollComplete'); 155 156 if ('open' === this.readyState) { 157 this.poll(); 158 } else { 159 debug('ignoring poll - transport state "%s"', this.readyState); 160 } 161 } 162 }; 163 164 /** 165 * For polling, send a close packet. 166 * 167 * @api private 168 */ 169 170 Polling.prototype.doClose = function () { 171 var self = this; 172 173 function close () { 174 debug('writing close packet'); 175 self.write([{ type: 'close' }]); 176 } 177 178 if ('open' === this.readyState) { 179 debug('transport open - closing'); 180 close(); 181 } else { 182 // in case we're trying to close while 183 // handshaking is in progress (GH-164) 184 debug('transport not open - deferring close'); 185 this.once('open', close); 186 } 187 }; 188 189 /** 190 * Writes a packets payload. 191 * 192 * @param {Array} data packets 193 * @param {Function} drain callback 194 * @api private 195 */ 196 197 Polling.prototype.write = function (packets) { 198 var self = this; 199 this.writable = false; 200 var callbackfn = function () { 201 self.writable = true; 202 self.emit('drain'); 203 }; 204 205 parser.encodePayload(packets, this.supportsBinary, function (data) { 206 self.doWrite(data, callbackfn); 207 }); 208 }; 209 210 /** 211 * Generates uri for connection. 212 * 213 * @api private 214 */ 215 216 Polling.prototype.uri = function () { 217 var query = this.query || {}; 218 var schema = this.secure ? 'https' : 'http'; 219 var port = ''; 220 221 // cache busting is forced 222 if (false !== this.timestampRequests) { 223 query[this.timestampParam] = yeast(); 224 } 225 226 if (!this.supportsBinary && !query.sid) { 227 query.b64 = 1; 228 } 229 230 query = parseqs.encode(query); 231 232 // avoid port if default for schema 233 if (this.port && (('https' === schema && Number(this.port) !== 443) || 234 ('http' === schema && Number(this.port) !== 80))) { 235 port = ':' + this.port; 236 } 237 238 // prepend ? to query 239 if (query.length) { 240 query = '?' + query; 241 } 242 243 var ipv6 = this.hostname.indexOf(':') !== -1; 244 return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query; 245 };