| /** |
| * Module dependencies. |
| */ |
| |
| var Transport = require('../transport'); |
| var parseqs = require('parseqs'); |
| var parser = require('engine.io-parser'); |
| var inherit = require('component-inherit'); |
| var yeast = require('yeast'); |
| var debug = require('debug')('engine.io-client:polling'); |
| |
| /** |
| * Module exports. |
| */ |
| |
| module.exports = Polling; |
| |
| /** |
| * Is XHR2 supported? |
| */ |
| |
| var hasXHR2 = (function () { |
| var XMLHttpRequest = require('xmlhttprequest-ssl'); |
| var xhr = new XMLHttpRequest({ xdomain: false }); |
| return null != xhr.responseType; |
| })(); |
| |
| /** |
| * Polling interface. |
| * |
| * @param {Object} opts |
| * @api private |
| */ |
| |
| function Polling (opts) { |
| var forceBase64 = (opts && opts.forceBase64); |
| if (!hasXHR2 || forceBase64) { |
| this.supportsBinary = false; |
| } |
| Transport.call(this, opts); |
| } |
| |
| /** |
| * Inherits from Transport. |
| */ |
| |
| inherit(Polling, Transport); |
| |
| /** |
| * Transport name. |
| */ |
| |
| Polling.prototype.name = 'polling'; |
| |
| /** |
| * Opens the socket (triggers polling). We write a PING message to determine |
| * when the transport is open. |
| * |
| * @api private |
| */ |
| |
| Polling.prototype.doOpen = function () { |
| this.poll(); |
| }; |
| |
| /** |
| * Pauses polling. |
| * |
| * @param {Function} callback upon buffers are flushed and transport is paused |
| * @api private |
| */ |
| |
| Polling.prototype.pause = function (onPause) { |
| var self = this; |
| |
| this.readyState = 'pausing'; |
| |
| function pause () { |
| debug('paused'); |
| self.readyState = 'paused'; |
| onPause(); |
| } |
| |
| if (this.polling || !this.writable) { |
| var total = 0; |
| |
| if (this.polling) { |
| debug('we are currently polling - waiting to pause'); |
| total++; |
| this.once('pollComplete', function () { |
| debug('pre-pause polling complete'); |
| --total || pause(); |
| }); |
| } |
| |
| if (!this.writable) { |
| debug('we are currently writing - waiting to pause'); |
| total++; |
| this.once('drain', function () { |
| debug('pre-pause writing complete'); |
| --total || pause(); |
| }); |
| } |
| } else { |
| pause(); |
| } |
| }; |
| |
| /** |
| * Starts polling cycle. |
| * |
| * @api public |
| */ |
| |
| Polling.prototype.poll = function () { |
| debug('polling'); |
| this.polling = true; |
| this.doPoll(); |
| this.emit('poll'); |
| }; |
| |
| /** |
| * Overloads onData to detect payloads. |
| * |
| * @api private |
| */ |
| |
| Polling.prototype.onData = function (data) { |
| var self = this; |
| debug('polling got data %s', data); |
| var callback = function (packet, index, total) { |
| // if its the first message we consider the transport open |
| if ('opening' === self.readyState) { |
| self.onOpen(); |
| } |
| |
| // if its a close packet, we close the ongoing requests |
| if ('close' === packet.type) { |
| self.onClose(); |
| return false; |
| } |
| |
| // otherwise bypass onData and handle the message |
| self.onPacket(packet); |
| }; |
| |
| // decode payload |
| parser.decodePayload(data, this.socket.binaryType, callback); |
| |
| // if an event did not trigger closing |
| if ('closed' !== this.readyState) { |
| // if we got data we're not polling |
| this.polling = false; |
| this.emit('pollComplete'); |
| |
| if ('open' === this.readyState) { |
| this.poll(); |
| } else { |
| debug('ignoring poll - transport state "%s"', this.readyState); |
| } |
| } |
| }; |
| |
| /** |
| * For polling, send a close packet. |
| * |
| * @api private |
| */ |
| |
| Polling.prototype.doClose = function () { |
| var self = this; |
| |
| function close () { |
| debug('writing close packet'); |
| self.write([{ type: 'close' }]); |
| } |
| |
| if ('open' === this.readyState) { |
| debug('transport open - closing'); |
| close(); |
| } else { |
| // in case we're trying to close while |
| // handshaking is in progress (GH-164) |
| debug('transport not open - deferring close'); |
| this.once('open', close); |
| } |
| }; |
| |
| /** |
| * Writes a packets payload. |
| * |
| * @param {Array} data packets |
| * @param {Function} drain callback |
| * @api private |
| */ |
| |
| Polling.prototype.write = function (packets) { |
| var self = this; |
| this.writable = false; |
| var callbackfn = function () { |
| self.writable = true; |
| self.emit('drain'); |
| }; |
| |
| parser.encodePayload(packets, this.supportsBinary, function (data) { |
| self.doWrite(data, callbackfn); |
| }); |
| }; |
| |
| /** |
| * Generates uri for connection. |
| * |
| * @api private |
| */ |
| |
| Polling.prototype.uri = function () { |
| var query = this.query || {}; |
| var schema = this.secure ? 'https' : 'http'; |
| var port = ''; |
| |
| // cache busting is forced |
| if (false !== this.timestampRequests) { |
| query[this.timestampParam] = yeast(); |
| } |
| |
| if (!this.supportsBinary && !query.sid) { |
| query.b64 = 1; |
| } |
| |
| query = parseqs.encode(query); |
| |
| // avoid port if default for schema |
| if (this.port && (('https' === schema && Number(this.port) !== 443) || |
| ('http' === schema && Number(this.port) !== 80))) { |
| port = ':' + this.port; |
| } |
| |
| // prepend ? to query |
| if (query.length) { |
| query = '?' + query; |
| } |
| |
| var ipv6 = this.hostname.indexOf(':') !== -1; |
| return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query; |
| }; |