const Transport = require("../transport"); const parseqs = require("parseqs"); const parser = require("engine.io-parser"); const yeast = require("yeast"); const debug = require("debug")("engine.io-client:polling"); class Polling extends Transport { /** * Transport name. */ get name() { return "polling"; } /** * Opens the socket (triggers polling). We write a PING message to determine * when the transport is open. * * @api private */ doOpen() { this.poll(); } /** * Pauses polling. * * @param {Function} callback upon buffers are flushed and transport is paused * @api private */ pause(onPause) { const self = this; this.readyState = "pausing"; function pause() { debug("paused"); self.readyState = "paused"; onPause(); } if (this.polling || !this.writable) { let 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 */ poll() { debug("polling"); this.polling = true; this.doPoll(); this.emit("poll"); } /** * Overloads onData to detect payloads. * * @api private */ onData(data) { const self = this; debug("polling got data %s", data); const callback = function(packet, index, total) { // if its the first message we consider the transport open if ("opening" === self.readyState && packet.type === "open") { 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).forEach(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 */ doClose() { const 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 */ write(packets) { this.writable = false; parser.encodePayload(packets, data => { this.doWrite(data, () => { this.writable = true; this.emit("drain"); }); }); } /** * Generates uri for connection. * * @api private */ uri() { let query = this.query || {}; const schema = this.opts.secure ? "https" : "http"; let port = ""; // cache busting is forced if (false !== this.opts.timestampRequests) { query[this.opts.timestampParam] = yeast(); } if (!this.supportsBinary && !query.sid) { query.b64 = 1; } query = parseqs.encode(query); // avoid port if default for schema if ( this.opts.port && (("https" === schema && Number(this.opts.port) !== 443) || ("http" === schema && Number(this.opts.port) !== 80)) ) { port = ":" + this.opts.port; } // prepend ? to query if (query.length) { query = "?" + query; } const ipv6 = this.opts.hostname.indexOf(":") !== -1; return ( schema + "://" + (ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) + port + this.opts.path + query ); } } module.exports = Polling;