123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- import { Transport } from "../transport.js";
- import parseqs from "parseqs";
- import yeast from "yeast";
- import { pick } from "../util.js";
- import { defaultBinaryType, 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.
- *
- * @api {Object} connection options
- * @api public
- */
- constructor(opts) {
- super(opts);
- this.supportsBinary = !opts.forceBase64;
- }
- /**
- * Transport name.
- *
- * @api public
- */
- get name() {
- return "websocket";
- }
- /**
- * Opens socket.
- *
- * @api private
- */
- 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.emit("error", err);
- }
- this.ws.binaryType = this.socket.binaryType || defaultBinaryType;
- this.addEventListeners();
- }
- /**
- * Adds event listeners to the socket
- *
- * @api private
- */
- addEventListeners() {
- this.ws.onopen = () => {
- if (this.opts.autoUnref) {
- this.ws._socket.unref();
- }
- this.onOpen();
- };
- this.ws.onclose = this.onClose.bind(this);
- this.ws.onmessage = ev => this.onData(ev.data);
- this.ws.onerror = e => this.onError("websocket error", e);
- }
- /**
- * Writes data to socket.
- *
- * @param {Array} array of packets.
- * @api private
- */
- 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 = "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.emit("drain");
- }, this.setTimeoutFn);
- }
- });
- }
- }
- /**
- * Closes socket.
- *
- * @api private
- */
- doClose() {
- if (typeof this.ws !== "undefined") {
- this.ws.close();
- this.ws = null;
- }
- }
- /**
- * Generates uri for connection.
- *
- * @api private
- */
- uri() {
- let query = this.query || {};
- const schema = this.opts.secure ? "wss" : "ws";
- let port = "";
- // avoid port if default for schema
- if (this.opts.port &&
- (("wss" === schema && Number(this.opts.port) !== 443) ||
- ("ws" === schema && Number(this.opts.port) !== 80))) {
- port = ":" + this.opts.port;
- }
- // append timestamp to URI
- if (this.opts.timestampRequests) {
- query[this.opts.timestampParam] = yeast();
- }
- // communicate binary support capabilities
- if (!this.supportsBinary) {
- query.b64 = 1;
- }
- const encodedQuery = parseqs.encode(query);
- const ipv6 = this.opts.hostname.indexOf(":") !== -1;
- return (schema +
- "://" +
- (ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) +
- port +
- this.opts.path +
- (encodedQuery.length ? "?" + encodedQuery : ""));
- }
- /**
- * Feature detection for WebSocket.
- *
- * @return {Boolean} whether this transport is available.
- * @api public
- */
- check() {
- return (!!WebSocket &&
- !("__initialize" in WebSocket && this.name === WS.prototype.name));
- }
- }
|