| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866 | "use strict";var __importDefault = (this && this.__importDefault) || function (mod) {    return (mod && mod.__esModule) ? mod : { "default": mod };};Object.defineProperty(exports, "__esModule", { value: true });exports.Socket = void 0;const socket_io_parser_1 = require("socket.io-parser");const on_js_1 = require("./on.js");const component_emitter_1 = require("@socket.io/component-emitter");const debug_1 = __importDefault(require("debug")); // debug()const debug = debug_1.default("socket.io-client:socket"); // debug()/** * Internal events. * These events can't be emitted by the user. */const RESERVED_EVENTS = Object.freeze({    connect: 1,    connect_error: 1,    disconnect: 1,    disconnecting: 1,    // EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener    newListener: 1,    removeListener: 1,});/** * A Socket is the fundamental class for interacting with the server. * * A Socket belongs to a certain Namespace (by default /) and uses an underlying {@link Manager} to communicate. * * @example * const socket = io(); * * socket.on("connect", () => { *   console.log("connected"); * }); * * // send an event to the server * socket.emit("foo", "bar"); * * socket.on("foobar", () => { *   // an event was received from the server * }); * * // upon disconnection * socket.on("disconnect", (reason) => { *   console.log(`disconnected due to ${reason}`); * }); */class Socket extends component_emitter_1.Emitter {    /**     * `Socket` constructor.     */    constructor(io, nsp, opts) {        super();        /**         * Whether the socket is currently connected to the server.         *         * @example         * const socket = io();         *         * socket.on("connect", () => {         *   console.log(socket.connected); // true         * });         *         * socket.on("disconnect", () => {         *   console.log(socket.connected); // false         * });         */        this.connected = false;        /**         * Whether the connection state was recovered after a temporary disconnection. In that case, any missed packets will         * be transmitted by the server.         */        this.recovered = false;        /**         * Buffer for packets received before the CONNECT packet         */        this.receiveBuffer = [];        /**         * Buffer for packets that will be sent once the socket is connected         */        this.sendBuffer = [];        /**         * The queue of packets to be sent with retry in case of failure.         *         * Packets are sent one by one, each waiting for the server acknowledgement, in order to guarantee the delivery order.         * @private         */        this._queue = [];        /**         * A sequence to generate the ID of the {@link QueuedPacket}.         * @private         */        this._queueSeq = 0;        this.ids = 0;        this.acks = {};        this.flags = {};        this.io = io;        this.nsp = nsp;        if (opts && opts.auth) {            this.auth = opts.auth;        }        this._opts = Object.assign({}, opts);        if (this.io._autoConnect)            this.open();    }    /**     * Whether the socket is currently disconnected     *     * @example     * const socket = io();     *     * socket.on("connect", () => {     *   console.log(socket.disconnected); // false     * });     *     * socket.on("disconnect", () => {     *   console.log(socket.disconnected); // true     * });     */    get disconnected() {        return !this.connected;    }    /**     * Subscribe to open, close and packet events     *     * @private     */    subEvents() {        if (this.subs)            return;        const io = this.io;        this.subs = [            on_js_1.on(io, "open", this.onopen.bind(this)),            on_js_1.on(io, "packet", this.onpacket.bind(this)),            on_js_1.on(io, "error", this.onerror.bind(this)),            on_js_1.on(io, "close", this.onclose.bind(this)),        ];    }    /**     * Whether the Socket will try to reconnect when its Manager connects or reconnects.     *     * @example     * const socket = io();     *     * console.log(socket.active); // true     *     * socket.on("disconnect", (reason) => {     *   if (reason === "io server disconnect") {     *     // the disconnection was initiated by the server, you need to manually reconnect     *     console.log(socket.active); // false     *   }     *   // else the socket will automatically try to reconnect     *   console.log(socket.active); // true     * });     */    get active() {        return !!this.subs;    }    /**     * "Opens" the socket.     *     * @example     * const socket = io({     *   autoConnect: false     * });     *     * socket.connect();     */    connect() {        if (this.connected)            return this;        this.subEvents();        if (!this.io["_reconnecting"])            this.io.open(); // ensure open        if ("open" === this.io._readyState)            this.onopen();        return this;    }    /**     * Alias for {@link connect()}.     */    open() {        return this.connect();    }    /**     * Sends a `message` event.     *     * This method mimics the WebSocket.send() method.     *     * @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send     *     * @example     * socket.send("hello");     *     * // this is equivalent to     * socket.emit("message", "hello");     *     * @return self     */    send(...args) {        args.unshift("message");        this.emit.apply(this, args);        return this;    }    /**     * Override `emit`.     * If the event is in `events`, it's emitted normally.     *     * @example     * socket.emit("hello", "world");     *     * // all serializable datastructures are supported (no need to call JSON.stringify)     * socket.emit("hello", 1, "2", { 3: ["4"], 5: Uint8Array.from([6]) });     *     * // with an acknowledgement from the server     * socket.emit("hello", "world", (val) => {     *   // ...     * });     *     * @return self     */    emit(ev, ...args) {        if (RESERVED_EVENTS.hasOwnProperty(ev)) {            throw new Error('"' + ev.toString() + '" is a reserved event name');        }        args.unshift(ev);        if (this._opts.retries && !this.flags.fromQueue && !this.flags.volatile) {            this._addToQueue(args);            return this;        }        const packet = {            type: socket_io_parser_1.PacketType.EVENT,            data: args,        };        packet.options = {};        packet.options.compress = this.flags.compress !== false;        // event ack callback        if ("function" === typeof args[args.length - 1]) {            const id = this.ids++;            debug("emitting packet with ack id %d", id);            const ack = args.pop();            this._registerAckCallback(id, ack);            packet.id = id;        }        const isTransportWritable = this.io.engine &&            this.io.engine.transport &&            this.io.engine.transport.writable;        const discardPacket = this.flags.volatile && (!isTransportWritable || !this.connected);        if (discardPacket) {            debug("discard packet as the transport is not currently writable");        }        else if (this.connected) {            this.notifyOutgoingListeners(packet);            this.packet(packet);        }        else {            this.sendBuffer.push(packet);        }        this.flags = {};        return this;    }    /**     * @private     */    _registerAckCallback(id, ack) {        var _a;        const timeout = (_a = this.flags.timeout) !== null && _a !== void 0 ? _a : this._opts.ackTimeout;        if (timeout === undefined) {            this.acks[id] = ack;            return;        }        // @ts-ignore        const timer = this.io.setTimeoutFn(() => {            delete this.acks[id];            for (let i = 0; i < this.sendBuffer.length; i++) {                if (this.sendBuffer[i].id === id) {                    debug("removing packet with ack id %d from the buffer", id);                    this.sendBuffer.splice(i, 1);                }            }            debug("event with ack id %d has timed out after %d ms", id, timeout);            ack.call(this, new Error("operation has timed out"));        }, timeout);        this.acks[id] = (...args) => {            // @ts-ignore            this.io.clearTimeoutFn(timer);            ack.apply(this, [null, ...args]);        };    }    /**     * Emits an event and waits for an acknowledgement     *     * @example     * // without timeout     * const response = await socket.emitWithAck("hello", "world");     *     * // with a specific timeout     * try {     *   const response = await socket.timeout(1000).emitWithAck("hello", "world");     * } catch (err) {     *   // the server did not acknowledge the event in the given delay     * }     *     * @return a Promise that will be fulfilled when the server acknowledges the event     */    emitWithAck(ev, ...args) {        // the timeout flag is optional        const withErr = this.flags.timeout !== undefined || this._opts.ackTimeout !== undefined;        return new Promise((resolve, reject) => {            args.push((arg1, arg2) => {                if (withErr) {                    return arg1 ? reject(arg1) : resolve(arg2);                }                else {                    return resolve(arg1);                }            });            this.emit(ev, ...args);        });    }    /**     * Add the packet to the queue.     * @param args     * @private     */    _addToQueue(args) {        let ack;        if (typeof args[args.length - 1] === "function") {            ack = args.pop();        }        const packet = {            id: this._queueSeq++,            tryCount: 0,            pending: false,            args,            flags: Object.assign({ fromQueue: true }, this.flags),        };        args.push((err, ...responseArgs) => {            if (packet !== this._queue[0]) {                // the packet has already been acknowledged                return;            }            const hasError = err !== null;            if (hasError) {                if (packet.tryCount > this._opts.retries) {                    debug("packet [%d] is discarded after %d tries", packet.id, packet.tryCount);                    this._queue.shift();                    if (ack) {                        ack(err);                    }                }            }            else {                debug("packet [%d] was successfully sent", packet.id);                this._queue.shift();                if (ack) {                    ack(null, ...responseArgs);                }            }            packet.pending = false;            return this._drainQueue();        });        this._queue.push(packet);        this._drainQueue();    }    /**     * Send the first packet of the queue, and wait for an acknowledgement from the server.     * @param force - whether to resend a packet that has not been acknowledged yet     *     * @private     */    _drainQueue(force = false) {        debug("draining queue");        if (!this.connected || this._queue.length === 0) {            return;        }        const packet = this._queue[0];        if (packet.pending && !force) {            debug("packet [%d] has already been sent and is waiting for an ack", packet.id);            return;        }        packet.pending = true;        packet.tryCount++;        debug("sending packet [%d] (try n°%d)", packet.id, packet.tryCount);        this.flags = packet.flags;        this.emit.apply(this, packet.args);    }    /**     * Sends a packet.     *     * @param packet     * @private     */    packet(packet) {        packet.nsp = this.nsp;        this.io._packet(packet);    }    /**     * Called upon engine `open`.     *     * @private     */    onopen() {        debug("transport is open - connecting");        if (typeof this.auth == "function") {            this.auth((data) => {                this._sendConnectPacket(data);            });        }        else {            this._sendConnectPacket(this.auth);        }    }    /**     * Sends a CONNECT packet to initiate the Socket.IO session.     *     * @param data     * @private     */    _sendConnectPacket(data) {        this.packet({            type: socket_io_parser_1.PacketType.CONNECT,            data: this._pid                ? Object.assign({ pid: this._pid, offset: this._lastOffset }, data)                : data,        });    }    /**     * Called upon engine or manager `error`.     *     * @param err     * @private     */    onerror(err) {        if (!this.connected) {            this.emitReserved("connect_error", err);        }    }    /**     * Called upon engine `close`.     *     * @param reason     * @param description     * @private     */    onclose(reason, description) {        debug("close (%s)", reason);        this.connected = false;        delete this.id;        this.emitReserved("disconnect", reason, description);    }    /**     * Called with socket packet.     *     * @param packet     * @private     */    onpacket(packet) {        const sameNamespace = packet.nsp === this.nsp;        if (!sameNamespace)            return;        switch (packet.type) {            case socket_io_parser_1.PacketType.CONNECT:                if (packet.data && packet.data.sid) {                    this.onconnect(packet.data.sid, packet.data.pid);                }                else {                    this.emitReserved("connect_error", new Error("It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)"));                }                break;            case socket_io_parser_1.PacketType.EVENT:            case socket_io_parser_1.PacketType.BINARY_EVENT:                this.onevent(packet);                break;            case socket_io_parser_1.PacketType.ACK:            case socket_io_parser_1.PacketType.BINARY_ACK:                this.onack(packet);                break;            case socket_io_parser_1.PacketType.DISCONNECT:                this.ondisconnect();                break;            case socket_io_parser_1.PacketType.CONNECT_ERROR:                this.destroy();                const err = new Error(packet.data.message);                // @ts-ignore                err.data = packet.data.data;                this.emitReserved("connect_error", err);                break;        }    }    /**     * Called upon a server event.     *     * @param packet     * @private     */    onevent(packet) {        const args = packet.data || [];        debug("emitting event %j", args);        if (null != packet.id) {            debug("attaching ack callback to event");            args.push(this.ack(packet.id));        }        if (this.connected) {            this.emitEvent(args);        }        else {            this.receiveBuffer.push(Object.freeze(args));        }    }    emitEvent(args) {        if (this._anyListeners && this._anyListeners.length) {            const listeners = this._anyListeners.slice();            for (const listener of listeners) {                listener.apply(this, args);            }        }        super.emit.apply(this, args);        if (this._pid && args.length && typeof args[args.length - 1] === "string") {            this._lastOffset = args[args.length - 1];        }    }    /**     * Produces an ack callback to emit with an event.     *     * @private     */    ack(id) {        const self = this;        let sent = false;        return function (...args) {            // prevent double callbacks            if (sent)                return;            sent = true;            debug("sending ack %j", args);            self.packet({                type: socket_io_parser_1.PacketType.ACK,                id: id,                data: args,            });        };    }    /**     * Called upon a server acknowlegement.     *     * @param packet     * @private     */    onack(packet) {        const ack = this.acks[packet.id];        if ("function" === typeof ack) {            debug("calling ack %s with %j", packet.id, packet.data);            ack.apply(this, packet.data);            delete this.acks[packet.id];        }        else {            debug("bad ack %s", packet.id);        }    }    /**     * Called upon server connect.     *     * @private     */    onconnect(id, pid) {        debug("socket connected with id %s", id);        this.id = id;        this.recovered = pid && this._pid === pid;        this._pid = pid; // defined only if connection state recovery is enabled        this.connected = true;        this.emitBuffered();        this.emitReserved("connect");        this._drainQueue(true);    }    /**     * Emit buffered events (received and emitted).     *     * @private     */    emitBuffered() {        this.receiveBuffer.forEach((args) => this.emitEvent(args));        this.receiveBuffer = [];        this.sendBuffer.forEach((packet) => {            this.notifyOutgoingListeners(packet);            this.packet(packet);        });        this.sendBuffer = [];    }    /**     * Called upon server disconnect.     *     * @private     */    ondisconnect() {        debug("server disconnect (%s)", this.nsp);        this.destroy();        this.onclose("io server disconnect");    }    /**     * Called upon forced client/server side disconnections,     * this method ensures the manager stops tracking us and     * that reconnections don't get triggered for this.     *     * @private     */    destroy() {        if (this.subs) {            // clean subscriptions to avoid reconnections            this.subs.forEach((subDestroy) => subDestroy());            this.subs = undefined;        }        this.io["_destroy"](this);    }    /**     * Disconnects the socket manually. In that case, the socket will not try to reconnect.     *     * If this is the last active Socket instance of the {@link Manager}, the low-level connection will be closed.     *     * @example     * const socket = io();     *     * socket.on("disconnect", (reason) => {     *   // console.log(reason); prints "io client disconnect"     * });     *     * socket.disconnect();     *     * @return self     */    disconnect() {        if (this.connected) {            debug("performing disconnect (%s)", this.nsp);            this.packet({ type: socket_io_parser_1.PacketType.DISCONNECT });        }        // remove socket from pool        this.destroy();        if (this.connected) {            // fire events            this.onclose("io client disconnect");        }        return this;    }    /**     * Alias for {@link disconnect()}.     *     * @return self     */    close() {        return this.disconnect();    }    /**     * Sets the compress flag.     *     * @example     * socket.compress(false).emit("hello");     *     * @param compress - if `true`, compresses the sending data     * @return self     */    compress(compress) {        this.flags.compress = compress;        return this;    }    /**     * Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not     * ready to send messages.     *     * @example     * socket.volatile.emit("hello"); // the server may or may not receive it     *     * @returns self     */    get volatile() {        this.flags.volatile = true;        return this;    }    /**     * Sets a modifier for a subsequent event emission that the callback will be called with an error when the     * given number of milliseconds have elapsed without an acknowledgement from the server:     *     * @example     * socket.timeout(5000).emit("my-event", (err) => {     *   if (err) {     *     // the server did not acknowledge the event in the given delay     *   }     * });     *     * @returns self     */    timeout(timeout) {        this.flags.timeout = timeout;        return this;    }    /**     * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the     * callback.     *     * @example     * socket.onAny((event, ...args) => {     *   console.log(`got ${event}`);     * });     *     * @param listener     */    onAny(listener) {        this._anyListeners = this._anyListeners || [];        this._anyListeners.push(listener);        return this;    }    /**     * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the     * callback. The listener is added to the beginning of the listeners array.     *     * @example     * socket.prependAny((event, ...args) => {     *   console.log(`got event ${event}`);     * });     *     * @param listener     */    prependAny(listener) {        this._anyListeners = this._anyListeners || [];        this._anyListeners.unshift(listener);        return this;    }    /**     * Removes the listener that will be fired when any event is emitted.     *     * @example     * const catchAllListener = (event, ...args) => {     *   console.log(`got event ${event}`);     * }     *     * socket.onAny(catchAllListener);     *     * // remove a specific listener     * socket.offAny(catchAllListener);     *     * // or remove all listeners     * socket.offAny();     *     * @param listener     */    offAny(listener) {        if (!this._anyListeners) {            return this;        }        if (listener) {            const listeners = this._anyListeners;            for (let i = 0; i < listeners.length; i++) {                if (listener === listeners[i]) {                    listeners.splice(i, 1);                    return this;                }            }        }        else {            this._anyListeners = [];        }        return this;    }    /**     * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,     * e.g. to remove listeners.     */    listenersAny() {        return this._anyListeners || [];    }    /**     * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the     * callback.     *     * Note: acknowledgements sent to the server are not included.     *     * @example     * socket.onAnyOutgoing((event, ...args) => {     *   console.log(`sent event ${event}`);     * });     *     * @param listener     */    onAnyOutgoing(listener) {        this._anyOutgoingListeners = this._anyOutgoingListeners || [];        this._anyOutgoingListeners.push(listener);        return this;    }    /**     * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the     * callback. The listener is added to the beginning of the listeners array.     *     * Note: acknowledgements sent to the server are not included.     *     * @example     * socket.prependAnyOutgoing((event, ...args) => {     *   console.log(`sent event ${event}`);     * });     *     * @param listener     */    prependAnyOutgoing(listener) {        this._anyOutgoingListeners = this._anyOutgoingListeners || [];        this._anyOutgoingListeners.unshift(listener);        return this;    }    /**     * Removes the listener that will be fired when any event is emitted.     *     * @example     * const catchAllListener = (event, ...args) => {     *   console.log(`sent event ${event}`);     * }     *     * socket.onAnyOutgoing(catchAllListener);     *     * // remove a specific listener     * socket.offAnyOutgoing(catchAllListener);     *     * // or remove all listeners     * socket.offAnyOutgoing();     *     * @param [listener] - the catch-all listener (optional)     */    offAnyOutgoing(listener) {        if (!this._anyOutgoingListeners) {            return this;        }        if (listener) {            const listeners = this._anyOutgoingListeners;            for (let i = 0; i < listeners.length; i++) {                if (listener === listeners[i]) {                    listeners.splice(i, 1);                    return this;                }            }        }        else {            this._anyOutgoingListeners = [];        }        return this;    }    /**     * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,     * e.g. to remove listeners.     */    listenersAnyOutgoing() {        return this._anyOutgoingListeners || [];    }    /**     * Notify the listeners for each packet sent     *     * @param packet     *     * @private     */    notifyOutgoingListeners(packet) {        if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {            const listeners = this._anyOutgoingListeners.slice();            for (const listener of listeners) {                listener.apply(this, packet.data);            }        }    }}exports.Socket = Socket;
 |