|
import { Transport } from "../transport.js"; |
|
import debugModule from "debug"; |
|
import { yeast } from "../contrib/yeast.js"; |
|
import { encodePayload, decodePayload } from "engine.io-parser"; |
|
import { createCookieJar, XHR as XMLHttpRequest, } from "./xmlhttprequest.js"; |
|
import { Emitter } from "@socket.io/component-emitter"; |
|
import { installTimerFunctions, pick } from "../util.js"; |
|
import { globalThisShim as globalThis } from "../globalThis.js"; |
|
const debug = debugModule("engine.io-client:polling"); |
|
function empty() { } |
|
const hasXHR2 = (function () { |
|
const xhr = new XMLHttpRequest({ |
|
xdomain: false, |
|
}); |
|
return null != xhr.responseType; |
|
})(); |
|
export class Polling extends Transport { |
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(opts) { |
|
super(opts); |
|
this.polling = false; |
|
if (typeof location !== "undefined") { |
|
const isSSL = "https:" === location.protocol; |
|
let port = location.port; |
|
|
|
if (!port) { |
|
port = isSSL ? "443" : "80"; |
|
} |
|
this.xd = |
|
(typeof location !== "undefined" && |
|
opts.hostname !== location.hostname) || |
|
port !== opts.port; |
|
} |
|
|
|
|
|
|
|
const forceBase64 = opts && opts.forceBase64; |
|
this.supportsBinary = hasXHR2 && !forceBase64; |
|
if (this.opts.withCredentials) { |
|
this.cookieJar = createCookieJar(); |
|
} |
|
} |
|
get name() { |
|
return "polling"; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
doOpen() { |
|
this.poll(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
pause(onPause) { |
|
this.readyState = "pausing"; |
|
const pause = () => { |
|
debug("paused"); |
|
this.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(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
poll() { |
|
debug("polling"); |
|
this.polling = true; |
|
this.doPoll(); |
|
this.emitReserved("poll"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
onData(data) { |
|
debug("polling got data %s", data); |
|
const callback = (packet) => { |
|
|
|
if ("opening" === this.readyState && packet.type === "open") { |
|
this.onOpen(); |
|
} |
|
|
|
if ("close" === packet.type) { |
|
this.onClose({ description: "transport closed by the server" }); |
|
return false; |
|
} |
|
|
|
this.onPacket(packet); |
|
}; |
|
|
|
decodePayload(data, this.socket.binaryType).forEach(callback); |
|
|
|
if ("closed" !== this.readyState) { |
|
|
|
this.polling = false; |
|
this.emitReserved("pollComplete"); |
|
if ("open" === this.readyState) { |
|
this.poll(); |
|
} |
|
else { |
|
debug('ignoring poll - transport state "%s"', this.readyState); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
doClose() { |
|
const close = () => { |
|
debug("writing close packet"); |
|
this.write([{ type: "close" }]); |
|
}; |
|
if ("open" === this.readyState) { |
|
debug("transport open - closing"); |
|
close(); |
|
} |
|
else { |
|
|
|
|
|
debug("transport not open - deferring close"); |
|
this.once("open", close); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
write(packets) { |
|
this.writable = false; |
|
encodePayload(packets, (data) => { |
|
this.doWrite(data, () => { |
|
this.writable = true; |
|
this.emitReserved("drain"); |
|
}); |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
uri() { |
|
const schema = this.opts.secure ? "https" : "http"; |
|
const query = this.query || {}; |
|
|
|
if (false !== this.opts.timestampRequests) { |
|
query[this.opts.timestampParam] = yeast(); |
|
} |
|
if (!this.supportsBinary && !query.sid) { |
|
query.b64 = 1; |
|
} |
|
return this.createUri(schema, query); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
request(opts = {}) { |
|
Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts); |
|
return new Request(this.uri(), opts); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
doWrite(data, fn) { |
|
const req = this.request({ |
|
method: "POST", |
|
data: data, |
|
}); |
|
req.on("success", fn); |
|
req.on("error", (xhrStatus, context) => { |
|
this.onError("xhr post error", xhrStatus, context); |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
doPoll() { |
|
debug("xhr poll"); |
|
const req = this.request(); |
|
req.on("data", this.onData.bind(this)); |
|
req.on("error", (xhrStatus, context) => { |
|
this.onError("xhr poll error", xhrStatus, context); |
|
}); |
|
this.pollXhr = req; |
|
} |
|
} |
|
export class Request extends Emitter { |
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(uri, opts) { |
|
super(); |
|
installTimerFunctions(this, opts); |
|
this.opts = opts; |
|
this.method = opts.method || "GET"; |
|
this.uri = uri; |
|
this.data = undefined !== opts.data ? opts.data : null; |
|
this.create(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
create() { |
|
var _a; |
|
const opts = pick(this.opts, "agent", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "autoUnref"); |
|
opts.xdomain = !!this.opts.xd; |
|
const xhr = (this.xhr = new XMLHttpRequest(opts)); |
|
try { |
|
debug("xhr open %s: %s", this.method, this.uri); |
|
xhr.open(this.method, this.uri, true); |
|
try { |
|
if (this.opts.extraHeaders) { |
|
xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true); |
|
for (let i in this.opts.extraHeaders) { |
|
if (this.opts.extraHeaders.hasOwnProperty(i)) { |
|
xhr.setRequestHeader(i, this.opts.extraHeaders[i]); |
|
} |
|
} |
|
} |
|
} |
|
catch (e) { } |
|
if ("POST" === this.method) { |
|
try { |
|
xhr.setRequestHeader("Content-type", "text/plain;charset=UTF-8"); |
|
} |
|
catch (e) { } |
|
} |
|
try { |
|
xhr.setRequestHeader("Accept", "*/*"); |
|
} |
|
catch (e) { } |
|
(_a = this.opts.cookieJar) === null || _a === void 0 ? void 0 : _a.addCookies(xhr); |
|
|
|
if ("withCredentials" in xhr) { |
|
xhr.withCredentials = this.opts.withCredentials; |
|
} |
|
if (this.opts.requestTimeout) { |
|
xhr.timeout = this.opts.requestTimeout; |
|
} |
|
xhr.onreadystatechange = () => { |
|
var _a; |
|
if (xhr.readyState === 3) { |
|
(_a = this.opts.cookieJar) === null || _a === void 0 ? void 0 : _a.parseCookies(xhr); |
|
} |
|
if (4 !== xhr.readyState) |
|
return; |
|
if (200 === xhr.status || 1223 === xhr.status) { |
|
this.onLoad(); |
|
} |
|
else { |
|
|
|
|
|
this.setTimeoutFn(() => { |
|
this.onError(typeof xhr.status === "number" ? xhr.status : 0); |
|
}, 0); |
|
} |
|
}; |
|
debug("xhr data %s", this.data); |
|
xhr.send(this.data); |
|
} |
|
catch (e) { |
|
|
|
|
|
|
|
this.setTimeoutFn(() => { |
|
this.onError(e); |
|
}, 0); |
|
return; |
|
} |
|
if (typeof document !== "undefined") { |
|
this.index = Request.requestsCount++; |
|
Request.requests[this.index] = this; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
onError(err) { |
|
this.emitReserved("error", err, this.xhr); |
|
this.cleanup(true); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
cleanup(fromError) { |
|
if ("undefined" === typeof this.xhr || null === this.xhr) { |
|
return; |
|
} |
|
this.xhr.onreadystatechange = empty; |
|
if (fromError) { |
|
try { |
|
this.xhr.abort(); |
|
} |
|
catch (e) { } |
|
} |
|
if (typeof document !== "undefined") { |
|
delete Request.requests[this.index]; |
|
} |
|
this.xhr = null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
onLoad() { |
|
const data = this.xhr.responseText; |
|
if (data !== null) { |
|
this.emitReserved("data", data); |
|
this.emitReserved("success"); |
|
this.cleanup(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
abort() { |
|
this.cleanup(); |
|
} |
|
} |
|
Request.requestsCount = 0; |
|
Request.requests = {}; |
|
|
|
|
|
|
|
|
|
|
|
if (typeof document !== "undefined") { |
|
|
|
if (typeof attachEvent === "function") { |
|
|
|
attachEvent("onunload", unloadHandler); |
|
} |
|
else if (typeof addEventListener === "function") { |
|
const terminationEvent = "onpagehide" in globalThis ? "pagehide" : "unload"; |
|
addEventListener(terminationEvent, unloadHandler, false); |
|
} |
|
} |
|
function unloadHandler() { |
|
for (let i in Request.requests) { |
|
if (Request.requests.hasOwnProperty(i)) { |
|
Request.requests[i].abort(); |
|
} |
|
} |
|
} |
|
|