feat: upgrade socket.io to v4

Signed-off-by: MiaoWoo <admin@yumc.pw>
This commit is contained in:
2022-11-21 23:18:39 +08:00
parent e563e1b507
commit df0d246136
45 changed files with 6585 additions and 1734 deletions

View File

@@ -0,0 +1,77 @@
/**
* Initialize backoff timer with `opts`.
*
* - `min` initial timeout in milliseconds [100]
* - `max` max timeout [10000]
* - `jitter` [0]
* - `factor` [2]
*
* @param {Object} opts
* @api public
*/
export function Backoff(this: any, opts) {
opts = opts || {}
this.ms = opts.min || 100
this.max = opts.max || 10000
this.factor = opts.factor || 2
this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0
this.attempts = 0
}
/**
* Return the backoff duration.
*
* @return {Number}
* @api public
*/
Backoff.prototype.duration = function () {
var ms = this.ms * Math.pow(this.factor, this.attempts++)
if (this.jitter) {
var rand = Math.random()
var deviation = Math.floor(rand * this.jitter * ms)
ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation
}
return Math.min(ms, this.max) | 0
}
/**
* Reset the number of attempts.
*
* @api public
*/
Backoff.prototype.reset = function () {
this.attempts = 0
}
/**
* Set the minimum duration
*
* @api public
*/
Backoff.prototype.setMin = function (min) {
this.ms = min
}
/**
* Set the maximum duration
*
* @api public
*/
Backoff.prototype.setMax = function (max) {
this.max = max
}
/**
* Set the jitter
*
* @api public
*/
Backoff.prototype.setJitter = function (jitter) {
this.jitter = jitter
}

View File

@@ -4,16 +4,10 @@ import { Socket, SocketOptions } from "./socket"
const debug = require("../debug")("socket.io-client")
/**
* Module exports.
*/
module.exports = exports = lookup
/**
* Managers cache.
*/
const cache: Record<string, Manager> = (exports.managers = {})
const cache: Record<string, Manager> = {}
/**
* Looks up an existing `Manager` for multiplexing.
@@ -76,6 +70,15 @@ function lookup(
return io.socket(parsed.path, opts)
}
// so that "lookup" can be used both as a function (e.g. `io(...)`) and as a
// namespace (e.g. `io.connect(...)`), for backward compatibility
Object.assign(lookup, {
Manager,
Socket,
io: lookup,
connect: lookup,
})
/**
* Protocol version.
*
@@ -84,22 +87,18 @@ function lookup(
export { protocol } from "../socket.io-parser"
/**
* `connect`.
*
* @param {String} uri
* @public
*/
exports.connect = lookup
/**
* Expose constructors for standalone build.
*
* @public
*/
export { Manager, ManagerOptions } from "./manager"
export { Socket } from "./socket"
export { lookup as io, SocketOptions }
export default lookup
export {
Manager,
ManagerOptions,
Socket,
SocketOptions,
lookup as io,
lookup as connect,
lookup as default,
}

View File

@@ -1,210 +1,26 @@
import eio from "../engine.io-client"
import { Socket, SocketOptions } from "./socket"
import {
Socket as Engine,
SocketOptions as EngineOptions,
installTimerFunctions,
nextTick,
} from "../engine.io-client"
import { Socket, SocketOptions, DisconnectDescription } from "./socket.js"
// import * as parser from "socket.io-parser"
import * as parser from "../socket.io-parser"
// import { Decoder, Encoder, Packet } from "socket.io-parser"
import { Decoder, Encoder, Packet } from "../socket.io-parser"
import { on } from "./on"
import * as Backoff from "backo2"
import { on } from "./on.js"
import { Backoff } from "./contrib/backo2"
import {
DefaultEventsMap,
EventsMap,
StrictEventEmitter,
} from "./typed-events"
Emitter,
} from "@socket.io/component-emitter"
// import debugModule from "debug" // debug()
// const debug = debugModule("socket.io-client:manager") // debug()
const debug = require("../debug")("socket.io-client")
interface EngineOptions {
/**
* The host that we're connecting to. Set from the URI passed when connecting
*/
host: string
/**
* The hostname for our connection. Set from the URI passed when connecting
*/
hostname: string
/**
* If this is a secure connection. Set from the URI passed when connecting
*/
secure: boolean
/**
* The port for our connection. Set from the URI passed when connecting
*/
port: string
/**
* Any query parameters in our uri. Set from the URI passed when connecting
*/
query: { [key: string]: string }
/**
* `http.Agent` to use, defaults to `false` (NodeJS only)
*/
agent: string | boolean
/**
* Whether the client should try to upgrade the transport from
* long-polling to something better.
* @default true
*/
upgrade: boolean
/**
* Forces JSONP for polling transport.
*/
forceJSONP: boolean
/**
* Determines whether to use JSONP when necessary for polling. If
* disabled (by settings to false) an error will be emitted (saying
* "No transports available") if no other transports are available.
* If another transport is available for opening a connection (e.g.
* WebSocket) that transport will be used instead.
* @default true
*/
jsonp: boolean
/**
* Forces base 64 encoding for polling transport even when XHR2
* responseType is available and WebSocket even if the used standard
* supports binary.
*/
forceBase64: boolean
/**
* Enables XDomainRequest for IE8 to avoid loading bar flashing with
* click sound. default to `false` because XDomainRequest has a flaw
* of not sending cookie.
* @default false
*/
enablesXDR: boolean
/**
* The param name to use as our timestamp key
* @default 't'
*/
timestampParam: string
/**
* Whether to add the timestamp with each transport request. Note: this
* is ignored if the browser is IE or Android, in which case requests
* are always stamped
* @default false
*/
timestampRequests: boolean
/**
* A list of transports to try (in order). Engine.io always attempts to
* connect directly with the first one, provided the feature detection test
* for it passes.
* @default ['polling','websocket']
*/
transports: string[]
/**
* The port the policy server listens on
* @default 843
*/
policyPost: number
/**
* If true and if the previous websocket connection to the server succeeded,
* the connection attempt will bypass the normal upgrade process and will
* initially try websocket. A connection attempt following a transport error
* will use the normal upgrade process. It is recommended you turn this on
* only when using SSL/TLS connections, or if you know that your network does
* not block websockets.
* @default false
*/
rememberUpgrade: boolean
/**
* Are we only interested in transports that support binary?
*/
onlyBinaryUpgrades: boolean
/**
* Timeout for xhr-polling requests in milliseconds (0) (only for polling transport)
*/
requestTimeout: number
/**
* Transport options for Node.js client (headers etc)
*/
transportOptions: Object
/**
* (SSL) Certificate, Private key and CA certificates to use for SSL.
* Can be used in Node.js client environment to manually specify
* certificate information.
*/
pfx: string
/**
* (SSL) Private key to use for SSL. Can be used in Node.js client
* environment to manually specify certificate information.
*/
key: string
/**
* (SSL) A string or passphrase for the private key or pfx. Can be
* used in Node.js client environment to manually specify certificate
* information.
*/
passphrase: string
/**
* (SSL) Public x509 certificate to use. Can be used in Node.js client
* environment to manually specify certificate information.
*/
cert: string
/**
* (SSL) An authority certificate or array of authority certificates to
* check the remote host against.. Can be used in Node.js client
* environment to manually specify certificate information.
*/
ca: string | string[]
/**
* (SSL) A string describing the ciphers to use or exclude. Consult the
* [cipher format list]
* (http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT) for
* details on the format.. Can be used in Node.js client environment to
* manually specify certificate information.
*/
ciphers: string
/**
* (SSL) If true, the server certificate is verified against the list of
* supplied CAs. An 'error' event is emitted if verification fails.
* Verification happens at the connection level, before the HTTP request
* is sent. Can be used in Node.js client environment to manually specify
* certificate information.
*/
rejectUnauthorized: boolean
/**
* Headers that will be passed for each request to the server (via xhr-polling and via websockets).
* These values then can be used during handshake or for special proxies.
*/
extraHeaders?: { [header: string]: string }
/**
* Whether to include credentials (cookies, authorization headers, TLS
* client certificates, etc.) with cross-origin XHR polling requests
* @default false
*/
withCredentials: boolean
/**
* Whether to automatically close the connection whenever the beforeunload event is received.
* @default true
*/
closeOnBeforeunload: boolean
}
export interface ManagerOptions extends EngineOptions {
/**
* Should we force a new Manager for this connection?
@@ -267,13 +83,6 @@ export interface ManagerOptions extends EngineOptions {
*/
autoConnect: boolean
/**
* weather we should unref the reconnect timer when it is
* create automatically
* @default false
*/
autoUnref: boolean
/**
* the parser to use. Defaults to an instance of the Parser that ships with socket.io.
*/
@@ -285,7 +94,7 @@ interface ManagerReservedEvents {
error: (err: Error) => void
ping: () => void
packet: (packet: Packet) => void
close: (reason: string) => void
close: (reason: string, description?: DisconnectDescription) => void
reconnect_failed: () => void
reconnect_attempt: (attempt: number) => void
reconnect_error: (err: Error) => void
@@ -295,13 +104,13 @@ interface ManagerReservedEvents {
export class Manager<
ListenEvents extends EventsMap = DefaultEventsMap,
EmitEvents extends EventsMap = ListenEvents
> extends StrictEventEmitter<{}, {}, ManagerReservedEvents> {
> extends Emitter<{}, {}, ManagerReservedEvents> {
/**
* The Engine.IO client instance
*
* @public
*/
public engine: any
public engine: Engine
/**
* @private
*/
@@ -320,7 +129,9 @@ export class Manager<
private nsps: Record<string, Socket> = {};
private subs: Array<ReturnType<typeof on>> = [];
// @ts-ignore
private backoff: Backoff
private setTimeoutFn: typeof setTimeout
private _reconnection: boolean
private _reconnectionAttempts: number
private _reconnectionDelay: number
@@ -358,6 +169,7 @@ export class Manager<
opts.path = opts.path || "/socket.io"
this.opts = opts
installTimerFunctions(this, opts)
this.reconnection(opts.reconnection !== false)
this.reconnectionAttempts(opts.reconnectionAttempts || Infinity)
this.reconnectionDelay(opts.reconnectionDelay || 1000)
@@ -507,8 +319,7 @@ export class Manager<
if (~this._readyState.indexOf("open")) return this
debug("opening %s", this.uri)
// @ts-ignore
this.engine = eio(this.uri, this.opts)
this.engine = new Engine(this.uri, this.opts)
const socket = this.engine
const self = this
this._readyState = "opening"
@@ -543,10 +354,11 @@ export class Manager<
}
// set timer
const timer = setTimeout(() => {
const timer = this.setTimeoutFn(() => {
debug("connect attempt timed out after %d", timeout)
openSubDestroy()
socket.close()
// @ts-ignore
socket.emit("error", new Error("timeout"))
}, timeout)
@@ -616,7 +428,11 @@ export class Manager<
* @private
*/
private ondata(data): void {
this.decoder.add(data)
try {
this.decoder.add(data)
} catch (e) {
this.onclose("parse error", e as Error)
}
}
/**
@@ -625,7 +441,10 @@ export class Manager<
* @private
*/
private ondecoded(packet): void {
this.emitReserved("packet", packet)
// the nextTick call prevents an exception in a user-provided event listener from triggering a disconnection due to a "parse error"
nextTick(() => {
this.emitReserved("packet", packet)
}, this.setTimeoutFn)
}
/**
@@ -713,13 +532,7 @@ export class Manager<
debug("disconnect")
this.skipReconnect = true
this._reconnecting = false
if ("opening" === this._readyState) {
// `onclose` will not fire because
// an open event never happened
this.cleanup()
}
this.backoff.reset()
this._readyState = "closed"
this.onclose("forced close")
if (this.engine) this.engine.close()
}
@@ -737,13 +550,13 @@ export class Manager<
*
* @private
*/
private onclose(reason: string): void {
debug("onclose")
private onclose(reason: string, description?: DisconnectDescription): void {
debug("closed due to %s", reason)
this.cleanup()
this.backoff.reset()
this._readyState = "closed"
this.emitReserved("close", reason)
this.emitReserved("close", reason, description)
if (this._reconnection && !this.skipReconnect) {
this.reconnect()
@@ -770,7 +583,7 @@ export class Manager<
debug("will wait %dms before reconnect attempt", delay)
this._reconnecting = true
const timer = setTimeout(() => {
const timer = this.setTimeoutFn(() => {
if (self.skipReconnect) return
debug("attempting reconnect")

View File

@@ -1,9 +1,7 @@
// import type * as Emitter from "component-emitter";
import { EventEmitter } from "events"
import { StrictEventEmitter } from "./typed-events"
import { Emitter } from "@socket.io/component-emitter"
export function on(
obj: EventEmitter | StrictEventEmitter<any, any>,
obj: Emitter<any, any>,
ev: string,
fn: (err?: any) => any
): VoidFunction {

View File

@@ -1,14 +1,17 @@
// import { Packet, PacketType } from "socket.io-parser"
import { Packet, PacketType } from "../socket.io-parser"
import { on } from "./on"
import { Manager } from "./manager"
import { on } from "./on.js"
import { Manager } from "./manager.js"
import {
DefaultEventsMap,
EventNames,
EventParams,
EventsMap,
StrictEventEmitter,
} from "./typed-events"
Emitter,
} from "@socket.io/component-emitter"
// import debugModule from "debug" // debug()
// const debug = debugModule("socket.io-client:socket") // debug()
const debug = require("../debug")("socket.io-client")
export interface SocketOptions {
@@ -35,26 +38,110 @@ const RESERVED_EVENTS = Object.freeze({
interface Flags {
compress?: boolean
volatile?: boolean
timeout?: number
}
export type DisconnectDescription =
| Error
| {
description: string
context?: CloseEvent | XMLHttpRequest
}
interface SocketReservedEvents {
connect: () => void
connect_error: (err: Error) => void
disconnect: (reason: Socket.DisconnectReason) => void
disconnect: (
reason: Socket.DisconnectReason,
description?: DisconnectDescription
) => void
}
/**
* 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}`);
* });
*/
export class Socket<
ListenEvents extends EventsMap = DefaultEventsMap,
EmitEvents extends EventsMap = ListenEvents
> extends StrictEventEmitter<ListenEvents, EmitEvents, SocketReservedEvents> {
> extends Emitter<ListenEvents, EmitEvents, SocketReservedEvents> {
public readonly io: Manager<ListenEvents, EmitEvents>
/**
* A unique identifier for the session.
*
* @example
* const socket = io();
*
* console.log(socket.id); // undefined
*
* socket.on("connect", () => {
* console.log(socket.id); // "G5p5..."
* });
*/
public id: string
public connected: boolean
public disconnected: boolean
/**
* 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
* });
*/
public connected: boolean = false;
/**
* Credentials that are sent when accessing a namespace.
*
* @example
* const socket = io({
* auth: {
* token: "abcd"
* }
* });
*
* // or with a function
* const socket = io({
* auth: (cb) => {
* cb({ token: localStorage.token })
* }
* });
*/
public auth: { [key: string]: any } | ((cb: (data: object) => void) => void)
/**
* Buffer for packets received before the CONNECT packet
*/
public receiveBuffer: Array<ReadonlyArray<any>> = [];
/**
* Buffer for packets that will be sent once the socket is connected
*/
public sendBuffer: Array<Packet> = [];
private readonly nsp: string
@@ -64,29 +151,39 @@ export class Socket<
private flags: Flags = {};
private subs?: Array<VoidFunction>
private _anyListeners: Array<(...args: any[]) => void>
private _anyOutgoingListeners: Array<(...args: any[]) => void>
/**
* `Socket` constructor.
*
* @public
*/
constructor(io: Manager, nsp: string, opts?: Partial<SocketOptions>) {
super()
this.io = io
this.nsp = nsp
this.ids = 0
this.acks = {}
this.receiveBuffer = []
this.sendBuffer = []
this.connected = false
this.disconnected = true
this.flags = {}
if (opts && opts.auth) {
this.auth = opts.auth
}
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
* });
*/
public get disconnected(): boolean {
return !this.connected
}
/**
* Subscribe to open, close and packet events
*
@@ -105,7 +202,21 @@ export class Socket<
}
/**
* Whether the Socket will try to reconnect when its Manager connects or reconnects
* 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
* });
*/
public get active(): boolean {
return !!this.subs
@@ -114,7 +225,12 @@ export class Socket<
/**
* "Opens" the socket.
*
* @public
* @example
* const socket = io({
* autoConnect: false
* });
*
* socket.connect();
*/
public connect(): this {
if (this.connected) return this
@@ -126,7 +242,7 @@ export class Socket<
}
/**
* Alias for connect()
* Alias for {@link connect()}.
*/
public open(): this {
return this.connect()
@@ -135,8 +251,17 @@ export class Socket<
/**
* 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
* @public
*/
public send(...args: any[]): this {
args.unshift("message")
@@ -149,15 +274,25 @@ export class Socket<
* 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
* @public
*/
public emit<Ev extends EventNames<EmitEvents>>(
ev: Ev,
...args: EventParams<EmitEvents, Ev>
): this {
if (RESERVED_EVENTS.hasOwnProperty(ev)) {
throw new Error('"' + ev + '" is a reserved event name')
throw new Error('"' + ev.toString() + '" is a reserved event name')
}
args.unshift(ev)
@@ -171,9 +306,12 @@ export class Socket<
// event ack callback
if ("function" === typeof args[args.length - 1]) {
debug("emitting packet with ack id %d", this.ids)
this.acks[this.ids] = args.pop()
packet.id = this.ids++
const id = this.ids++
debug("emitting packet with ack id %d", id)
const ack = args.pop() as Function
this._registerAckCallback(id, ack)
packet.id = id
}
const isTransportWritable =
@@ -186,6 +324,7 @@ export class Socket<
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)
@@ -196,6 +335,36 @@ export class Socket<
return this
}
/**
* @private
*/
private _registerAckCallback(id: number, ack: Function) {
const timeout = this.flags.timeout
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])
}
}
/**
* Sends a packet.
*
@@ -239,14 +408,17 @@ export class Socket<
* Called upon engine `close`.
*
* @param reason
* @param description
* @private
*/
private onclose(reason: Socket.DisconnectReason): void {
private onclose(
reason: Socket.DisconnectReason,
description?: DisconnectDescription
): void {
debug("close (%s)", reason)
this.connected = false
this.disconnected = true
delete this.id
this.emitReserved("disconnect", reason)
this.emitReserved("disconnect", reason, description)
}
/**
@@ -276,17 +448,11 @@ export class Socket<
break
case PacketType.EVENT:
this.onevent(packet)
break
case PacketType.BINARY_EVENT:
this.onevent(packet)
break
case PacketType.ACK:
this.onack(packet)
break
case PacketType.BINARY_ACK:
this.onack(packet)
break
@@ -296,6 +462,7 @@ export class Socket<
break
case PacketType.CONNECT_ERROR:
this.destroy()
const err = new Error(packet.data.message)
// @ts-ignore
err.data = packet.data.data
@@ -386,7 +553,6 @@ export class Socket<
debug("socket connected with id %s", id)
this.id = id
this.connected = true
this.disconnected = false
this.emitBuffered()
this.emitReserved("connect")
}
@@ -400,7 +566,10 @@ export class Socket<
this.receiveBuffer.forEach((args) => this.emitEvent(args))
this.receiveBuffer = []
this.sendBuffer.forEach((packet) => this.packet(packet))
this.sendBuffer.forEach((packet) => {
this.notifyOutgoingListeners(packet)
this.packet(packet)
})
this.sendBuffer = []
}
@@ -432,10 +601,20 @@ export class Socket<
}
/**
* Disconnects the socket manually.
* 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
* @public
*/
public disconnect(): this {
if (this.connected) {
@@ -454,10 +633,9 @@ export class Socket<
}
/**
* Alias for disconnect()
* Alias for {@link disconnect()}.
*
* @return self
* @public
*/
public close(): this {
return this.disconnect()
@@ -466,9 +644,11 @@ export class Socket<
/**
* Sets the compress flag.
*
* @example
* socket.compress(false).emit("hello");
*
* @param compress - if `true`, compresses the sending data
* @return self
* @public
*/
public compress(compress: boolean): this {
this.flags.compress = compress
@@ -479,20 +659,44 @@ export class Socket<
* 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
* @public
*/
public get volatile(): this {
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
*/
public timeout(timeout: number): this {
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
* @public
*/
public onAny(listener: (...args: any[]) => void): this {
this._anyListeners = this._anyListeners || []
@@ -504,8 +708,12 @@ export class Socket<
* 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
* @public
*/
public prependAny(listener: (...args: any[]) => void): this {
this._anyListeners = this._anyListeners || []
@@ -516,8 +724,20 @@ export class Socket<
/**
* 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
* @public
*/
public offAny(listener?: (...args: any[]) => void): this {
if (!this._anyListeners) {
@@ -540,12 +760,108 @@ export class Socket<
/**
* Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
* e.g. to remove listeners.
*
* @public
*/
public 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
*/
public onAnyOutgoing(listener: (...args: any[]) => void): this {
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
*/
public prependAnyOutgoing(listener: (...args: any[]) => void): this {
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)
*/
public offAnyOutgoing(listener?: (...args: any[]) => void): this {
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.
*/
public listenersAnyOutgoing() {
return this._anyOutgoingListeners || []
}
/**
* Notify the listeners for each packet sent
*
* @param packet
*
* @private
*/
private notifyOutgoingListeners(packet: Packet) {
if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {
const listeners = this._anyOutgoingListeners.slice()
for (const listener of listeners) {
listener.apply(this, packet.data)
}
}
}
}
export namespace Socket {
@@ -555,4 +871,5 @@ export namespace Socket {
| "ping timeout"
| "transport close"
| "transport error"
| "parse error"
}

View File

@@ -1,157 +0,0 @@
import { EventEmitter } from "events"
/**
* An events map is an interface that maps event names to their value, which
* represents the type of the `on` listener.
*/
export interface EventsMap {
[event: string]: any
}
/**
* The default events map, used if no EventsMap is given. Using this EventsMap
* is equivalent to accepting all event names, and any data.
*/
export interface DefaultEventsMap {
[event: string]: (...args: any[]) => void
}
/**
* Returns a union type containing all the keys of an event map.
*/
export type EventNames<Map extends EventsMap> = keyof Map & (string | symbol)
/** The tuple type representing the parameters of an event listener */
export type EventParams<
Map extends EventsMap,
Ev extends EventNames<Map>
> = Parameters<Map[Ev]>
/**
* The event names that are either in ReservedEvents or in UserEvents
*/
export type ReservedOrUserEventNames<
ReservedEventsMap extends EventsMap,
UserEvents extends EventsMap
> = EventNames<ReservedEventsMap> | EventNames<UserEvents>
/**
* Type of a listener of a user event or a reserved event. If `Ev` is in
* `ReservedEvents`, the reserved event listener is returned.
*/
export type ReservedOrUserListener<
ReservedEvents extends EventsMap,
UserEvents extends EventsMap,
Ev extends ReservedOrUserEventNames<ReservedEvents, UserEvents>
> = FallbackToUntypedListener<
Ev extends EventNames<ReservedEvents>
? ReservedEvents[Ev]
: Ev extends EventNames<UserEvents>
? UserEvents[Ev]
: never
>
/**
* Returns an untyped listener type if `T` is `never`; otherwise, returns `T`.
*
* This is a hack to mitigate https://github.com/socketio/socket.io/issues/3833.
* Needed because of https://github.com/microsoft/TypeScript/issues/41778
*/
type FallbackToUntypedListener<T> = [T] extends [never]
? (...args: any[]) => void
: T
/**
* Strictly typed version of an `EventEmitter`. A `TypedEventEmitter` takes type
* parameters for mappings of event names to event data types, and strictly
* types method calls to the `EventEmitter` according to these event maps.
*
* @typeParam ListenEvents - `EventsMap` of user-defined events that can be
* listened to with `on` or `once`
* @typeParam EmitEvents - `EventsMap` of user-defined events that can be
* emitted with `emit`
* @typeParam ReservedEvents - `EventsMap` of reserved events, that can be
* emitted by socket.io with `emitReserved`, and can be listened to with
* `listen`.
*/
export abstract class StrictEventEmitter<
ListenEvents extends EventsMap,
EmitEvents extends EventsMap,
ReservedEvents extends EventsMap = {}
> extends EventEmitter {
/**
* Adds the `listener` function as an event listener for `ev`.
*
* @param ev Name of the event
* @param listener Callback function
*/
on<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
ev: Ev,
listener: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>
): this {
super.on(ev as string, listener)
return this
}
/**
* Adds a one-time `listener` function as an event listener for `ev`.
*
* @param ev Name of the event
* @param listener Callback function
*/
once<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
ev: Ev,
listener: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>
): this {
super.once(ev as string, listener)
return this
}
/**
* Emits an event.
*
* @param ev Name of the event
* @param args Values to send to listeners of this event
*/
// @ts-ignore
emit<Ev extends EventNames<EmitEvents>>(
ev: Ev,
...args: EventParams<EmitEvents, Ev>
): this {
super.emit(ev as string, ...args)
return this
}
/**
* Emits a reserved event.
*
* This method is `protected`, so that only a class extending
* `StrictEventEmitter` can emit its own reserved events.
*
* @param ev Reserved event name
* @param args Arguments to emit along with the event
*/
protected emitReserved<Ev extends EventNames<ReservedEvents>>(
ev: Ev,
...args: EventParams<ReservedEvents, Ev>
): this {
super.emit(ev as string, ...args)
return this
}
/**
* Returns the listeners listening to an event.
*
* @param event Event name
* @returns Array of listeners subscribed to `event`
*/
listeners<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
event: Ev
): ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>[] {
return super.listeners(event as string) as ReservedOrUserListener<
ReservedEvents,
ListenEvents,
Ev
>[]
}
}

View File

@@ -1,5 +1,8 @@
import * as parseuri from "parseuri"
// import { parse } from "engine.io-client"
import { parse } from "../engine.io-client"
// import debugModule from "debug" // debug()
// const debug = debugModule("socket.io-client:url"); // debug()
const debug = require("../debug")("socket.io-client")
type ParsedUrl = {
@@ -67,7 +70,7 @@ export function url(
// parse
debug("parse %s", uri)
obj = parseuri(uri) as ParsedUrl
obj = parse(uri) as ParsedUrl
}
// make sure we treat `localhost:80` and `localhost` equally