import { EventEmitter } from "events"; import * as parser_v4 from "engine.io-parser"; import * as parser_v3 from "./parser-v3/index"; import debugModule from "debug"; import type { IncomingMessage, ServerResponse } from "http"; import { Packet, RawData } from "engine.io-parser"; import type { WebSocket } from "ws"; const debug = debugModule("engine:transport"); function noop() {} type ReadyState = "open" | "closing" | "closed"; export type EngineRequest = IncomingMessage & { _query: Record; res?: ServerResponse; cleanup?: Function; websocket?: WebSocket & { _socket?: { remoteAddress: string; }; }; }; export abstract class Transport extends EventEmitter { /** * The session ID. */ public sid: string; /** * Whether the transport is currently ready to send packets. */ public writable = false; /** * The revision of the protocol: * * - 3 is used in Engine.IO v3 / Socket.IO v2 * - 4 is used in Engine.IO v4 and above / Socket.IO v3 and above * * It is found in the `EIO` query parameters of the HTTP requests. * * @see https://github.com/socketio/engine.io-protocol */ public protocol: 3 | 4; /** * The current state of the transport. * @protected */ protected _readyState: ReadyState = "open"; /** * Whether the transport is discarded and can be safely closed (used during upgrade). * @protected */ protected discarded = false; /** * The parser to use (depends on the revision of the {@link Transport#protocol}. * @protected */ protected parser: typeof parser_v4 | typeof parser_v3; /** * Whether the transport supports binary payloads (else it will be base64-encoded) * @protected */ protected supportsBinary: boolean; get readyState() { return this._readyState; } set readyState(state: ReadyState) { debug( "readyState updated from %s to %s (%s)", this._readyState, state, this.name, ); this._readyState = state; } /** * The list of transports this transport can be upgraded to. */ static upgradesTo: string[] = []; /** * Transport constructor. * * @param {EngineRequest} req */ constructor(req: { _query: Record }) { super(); this.protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default this.parser = this.protocol === 4 ? parser_v4 : parser_v3; this.supportsBinary = !(req._query && req._query.b64); } /** * Flags the transport as discarded. * * @package */ discard() { this.discarded = true; } /** * Called with an incoming HTTP request. * * @param req * @package */ onRequest(req: any) {} /** * Closes the transport. * * @package */ close(fn?: () => void) { if ("closed" === this.readyState || "closing" === this.readyState) return; this.readyState = "closing"; this.doClose(fn || noop); } /** * Called with a transport error. * * @param {String} msg - message error * @param {Object} desc - error description * @protected */ protected onError(msg: string, desc?) { if (this.listeners("error").length) { const err = new Error(msg); // @ts-ignore err.type = "TransportError"; // @ts-ignore err.description = desc; this.emit("error", err); } else { debug("ignored transport error %s (%s)", msg, desc); } } /** * Called with parsed out a packets from the data stream. * * @param {Object} packet * @protected */ protected onPacket(packet: Packet) { this.emit("packet", packet); } /** * Called with the encoded packet data. * * @param data * @protected */ protected onData(data: RawData) { this.onPacket(this.parser.decodePacket(data)); } /** * Called upon transport close. * * @protected */ protected onClose() { this.readyState = "closed"; this.emit("close"); } /** * The name of the transport. */ abstract get name(): string; /** * Sends an array of packets. * * @param {Array} packets * @package */ abstract send(packets: Packet[]): void; /** * Closes the transport. */ abstract doClose(fn?: () => void): void; }