File size: 5,321 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
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";
// detect ReactNative environment
const isReactNative = typeof navigator !== "undefined" &&
typeof navigator.product === "string" &&
navigator.product.toLowerCase() === "reactnative";
export class WS extends Transport {
/**
* WebSocket transport constructor.
*
* @param {Object} opts - connection options
* @protected
*/
constructor(opts) {
super(opts);
this.supportsBinary = !opts.forceBase64;
}
get name() {
return "websocket";
}
doOpen() {
if (!this.check()) {
// let probe timeout
return;
}
const uri = this.uri();
const protocols = this.opts.protocols;
// React Native only supports the 'headers' option, and will print a warning if anything else is passed
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();
}
/**
* Adds event listeners to the socket
*
* @private
*/
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;
// encodePacket efficient as it uses WS framing
// no need for encodePayload
for (let i = 0; i < packets.length; i++) {
const packet = packets[i];
const lastPacket = i === packets.length - 1;
encodePacket(packet, this.supportsBinary, (data) => {
// always create a new object (GH-437)
const opts = {};
if (!usingBrowserWebSocket) {
if (packet.options) {
opts.compress = packet.options.compress;
}
if (this.opts.perMessageDeflate) {
const len =
// @ts-ignore
"string" === typeof data ? Buffer.byteLength(data) : data.length;
if (len < this.opts.perMessageDeflate.threshold) {
opts.compress = false;
}
}
}
// Sometimes the websocket has already been closed but the browser didn't
// have a chance of informing us about it yet, in that case send will
// throw an error
try {
if (usingBrowserWebSocket) {
// TypeError is thrown when passing the second argument on Safari
this.ws.send(data);
}
else {
this.ws.send(data, opts);
}
}
catch (e) {
}
if (lastPacket) {
// fake drain
// defer to next tick to allow Socket to clear writeBuffer
nextTick(() => {
this.writable = true;
this.emitReserved("drain");
}, this.setTimeoutFn);
}
});
}
}
doClose() {
if (typeof this.ws !== "undefined") {
this.ws.close();
this.ws = null;
}
}
/**
* Generates uri for connection.
*
* @private
*/
uri() {
const schema = this.opts.secure ? "wss" : "ws";
const query = this.query || {};
// append timestamp to URI
if (this.opts.timestampRequests) {
query[this.opts.timestampParam] = yeast();
}
// communicate binary support capabilities
if (!this.supportsBinary) {
query.b64 = 1;
}
return this.createUri(schema, query);
}
/**
* Feature detection for WebSocket.
*
* @return {Boolean} whether this transport is available.
* @private
*/
check() {
return !!WebSocket;
}
}
|