|
import { Transport } from "../transport.js"; |
|
import { yeast } from "../contrib/yeast.js"; |
|
import { pick } from "../util.js"; |
|
import { nextTick, usingBrowserWebSocket, WebSocket, } from "./websocket-constructor.js"; |
|
import { encodePacket } from "engine.io-parser"; |
|
|
|
const isReactNative = typeof navigator !== "undefined" && |
|
typeof navigator.product === "string" && |
|
navigator.product.toLowerCase() === "reactnative"; |
|
export class WS extends Transport { |
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(opts) { |
|
super(opts); |
|
this.supportsBinary = !opts.forceBase64; |
|
} |
|
get name() { |
|
return "websocket"; |
|
} |
|
doOpen() { |
|
if (!this.check()) { |
|
|
|
return; |
|
} |
|
const uri = this.uri(); |
|
const protocols = this.opts.protocols; |
|
|
|
const opts = isReactNative |
|
? {} |
|
: pick(this.opts, "agent", "perMessageDeflate", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "localAddress", "protocolVersion", "origin", "maxPayload", "family", "checkServerIdentity"); |
|
if (this.opts.extraHeaders) { |
|
opts.headers = this.opts.extraHeaders; |
|
} |
|
try { |
|
this.ws = |
|
usingBrowserWebSocket && !isReactNative |
|
? protocols |
|
? new WebSocket(uri, protocols) |
|
: new WebSocket(uri) |
|
: new WebSocket(uri, protocols, opts); |
|
} |
|
catch (err) { |
|
return this.emitReserved("error", err); |
|
} |
|
this.ws.binaryType = this.socket.binaryType; |
|
this.addEventListeners(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
addEventListeners() { |
|
this.ws.onopen = () => { |
|
if (this.opts.autoUnref) { |
|
this.ws._socket.unref(); |
|
} |
|
this.onOpen(); |
|
}; |
|
this.ws.onclose = (closeEvent) => this.onClose({ |
|
description: "websocket connection closed", |
|
context: closeEvent, |
|
}); |
|
this.ws.onmessage = (ev) => this.onData(ev.data); |
|
this.ws.onerror = (e) => this.onError("websocket error", e); |
|
} |
|
write(packets) { |
|
this.writable = false; |
|
|
|
|
|
for (let i = 0; i < packets.length; i++) { |
|
const packet = packets[i]; |
|
const lastPacket = i === packets.length - 1; |
|
encodePacket(packet, this.supportsBinary, (data) => { |
|
|
|
const opts = {}; |
|
if (!usingBrowserWebSocket) { |
|
if (packet.options) { |
|
opts.compress = packet.options.compress; |
|
} |
|
if (this.opts.perMessageDeflate) { |
|
const len = |
|
|
|
"string" === typeof data ? Buffer.byteLength(data) : data.length; |
|
if (len < this.opts.perMessageDeflate.threshold) { |
|
opts.compress = false; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
try { |
|
if (usingBrowserWebSocket) { |
|
|
|
this.ws.send(data); |
|
} |
|
else { |
|
this.ws.send(data, opts); |
|
} |
|
} |
|
catch (e) { |
|
} |
|
if (lastPacket) { |
|
|
|
|
|
nextTick(() => { |
|
this.writable = true; |
|
this.emitReserved("drain"); |
|
}, this.setTimeoutFn); |
|
} |
|
}); |
|
} |
|
} |
|
doClose() { |
|
if (typeof this.ws !== "undefined") { |
|
this.ws.close(); |
|
this.ws = null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
uri() { |
|
const schema = this.opts.secure ? "wss" : "ws"; |
|
const query = this.query || {}; |
|
|
|
if (this.opts.timestampRequests) { |
|
query[this.opts.timestampParam] = yeast(); |
|
} |
|
|
|
if (!this.supportsBinary) { |
|
query.b64 = 1; |
|
} |
|
return this.createUri(schema, query); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
check() { |
|
return !!WebSocket; |
|
} |
|
} |
|
|