From dc381b72c6b2f8172001dedd84116122e4cc95b3 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Mon, 10 May 2021 11:05:28 +0200 Subject: [PATCH 1/5] perf: add support for the "wsPreEncoded" writing option Packets that are sent to multiple clients will now be pre-encoded for the WebSocket transport (which means simply prepending "4" - which is the "message" packet type in Engine.IO). Note: buffers are not pre-encoded, since they are sent without modification over the WebSocket connection See also: https://github.com/socketio/engine.io/commit/7706b123df914777d19c8179b45ab6932f82916c engine.io diff: https://github.com/socketio/engine.io/compare/5.0.0...5.1.0 --- lib/client.ts | 47 +++++++++++++++++++++++++---------------------- package-lock.json | 12 ++++++------ package.json | 4 ++-- test/socket.io.ts | 22 ++++++++++++++++++++++ 4 files changed, 55 insertions(+), 30 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index 7d4ef9151e..3693744cce 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -10,6 +10,12 @@ import type { SocketId } from "socket.io-adapter"; const debug = debugModule("socket.io:client"); +interface WriteOptions { + compress?: boolean; + volatile?: boolean; + wsPreEncoded?: string; +} + export class Client< ListenEvents extends EventsMap, EmitEvents extends EventsMap @@ -180,31 +186,28 @@ export class Client< * @param {Object} opts * @private */ - _packet(packet: Packet, opts?: any): void { - opts = opts || {}; - const self = this; - - // this writes to the actual connection - function writeToEngine(encodedPackets: any) { - // TODO clarify this. - if (opts.volatile && !self.conn.transport.writable) return; - for (let i = 0; i < encodedPackets.length; i++) { - self.conn.write(encodedPackets[i], { compress: opts.compress }); - } + _packet(packet: Packet, opts: WriteOptions = {}): void { + if (this.conn.readyState !== "open") { + debug("ignoring packet write %j", packet); + return; + } + const encodedPackets = this.encoder.encode(packet); + for (const encodedPacket of encodedPackets) { + this.writeToEngine(encodedPacket, opts); } + } - if ("open" === this.conn.readyState) { - debug("writing packet %j", packet); - if (!opts.preEncoded) { - // not broadcasting, need to encode - writeToEngine(this.encoder.encode(packet)); // encode, then write results to engine - } else { - // a broadcast pre-encodes a packet - writeToEngine(packet); - } - } else { - debug("ignoring packet write %j", packet); + private writeToEngine( + encodedPacket: String | Buffer, + opts: WriteOptions + ): void { + if (opts.volatile && !this.conn.transport.writable) { + debug( + "volatile packet is discarded since the transport is not currently writable" + ); + return; } + this.conn.write(encodedPacket, opts); } /** diff --git a/package-lock.json b/package-lock.json index 48a47caf31..0aeea39007 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1206,9 +1206,9 @@ } }, "engine.io": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-5.0.0.tgz", - "integrity": "sha512-BATIdDV3H1SrE9/u2BAotvsmjJg0t1P4+vGedImSs1lkFAtQdvk4Ev1y4LDiPF7BPWgXWEG+NDY+nLvW3UrMWw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-5.1.0.tgz", + "integrity": "sha512-A2i4kVvOA3qezQLlMz+FayGFdqOo0LP3fYrb0VqXMDXKoXcbgM0KxcEYnsdVzOMJQErIAb1GIStRj7UWFoiqlQ==", "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", @@ -3330,9 +3330,9 @@ } }, "socket.io-adapter": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.2.0.tgz", - "integrity": "sha512-rG49L+FwaVEwuAdeBRq49M97YI3ElVabJPzvHT9S6a2CWhDKnjSFasvwAwSYPRhQzfn4NtDIbCaGYgOCOU/rlg==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.0.tgz", + "integrity": "sha512-jdIbSFRWOkaZpo5mXy8T7rXEN6qo3bOFuq4nVeX1ZS7AtFlkbk39y153xTXEIW7W94vZfhVOux1wTU88YxcM1w==" }, "socket.io-client": { "version": "4.0.2", diff --git a/package.json b/package.json index 1ae020879c..5f62de0bf6 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,8 @@ "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.1", - "engine.io": "~5.0.0", - "socket.io-adapter": "~2.2.0", + "engine.io": "~5.1.0", + "socket.io-adapter": "~2.3.0", "socket.io-parser": "~4.0.3" }, "devDependencies": { diff --git a/test/socket.io.ts b/test/socket.io.ts index 48f4a76cee..84c57d1acb 100644 --- a/test/socket.io.ts +++ b/test/socket.io.ts @@ -2385,6 +2385,28 @@ describe("socket.io", () => { }); }); }); + + it("should pre encode a broadcast packet", (done) => { + const srv = createServer(); + const sio = new Server(srv); + + srv.listen(() => { + const clientSocket = client(srv, { multiplex: false }); + + sio.on("connection", (socket) => { + socket.conn.on("packetCreate", (packet) => { + expect(packet.data).to.eql('2["hello","world"]'); + expect(packet.options.wsPreEncoded).to.eql('42["hello","world"]'); + + clientSocket.close(); + sio.close(); + done(); + }); + + sio.emit("hello", "world"); + }); + }); + }); }); describe("middleware", () => { From 93cce05fb3faf91f21fa71212275c776aa161107 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Mon, 10 May 2021 12:06:20 +0200 Subject: [PATCH 2/5] feat: add support for inter-server communication Syntax: ```js // server A io.serverSideEmit("hello", "world"); // server B io.on("hello", (arg) => { console.log(arg); // prints "world" }); ``` With acknowledgements: ```js // server A io.serverSideEmit("hello", "world", (err, responses) => { console.log(responses); // prints ["hi"] }); // server B io.on("hello", (arg, callback) => { callback("hi"); }); ``` This feature replaces the customHook/customRequest API from the Redis adapter: https://github.com/socketio/socket.io-redis/issues/370 --- lib/client.ts | 28 ++++++++++++---- lib/index.ts | 49 +++++++++++++++++++++------ lib/namespace.ts | 72 +++++++++++++++++++++++++++++++--------- lib/parent-namespace.ts | 15 ++++++--- lib/socket.ts | 11 +++--- test/socket.io.test-d.ts | 44 ++++++++++++++++++++++++ 6 files changed, 176 insertions(+), 43 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index 3693744cce..a9c740d3e5 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -18,16 +18,23 @@ interface WriteOptions { export class Client< ListenEvents extends EventsMap, - EmitEvents extends EventsMap + EmitEvents extends EventsMap, + ServerSideEvents extends EventsMap > { public readonly conn; private readonly id: string; - private readonly server: Server; + private readonly server: Server; private readonly encoder: Encoder; private readonly decoder: Decoder; - private sockets: Map> = new Map(); - private nsps: Map> = new Map(); + private sockets: Map< + SocketId, + Socket + > = new Map(); + private nsps: Map< + string, + Socket + > = new Map(); private connectTimeout?: NodeJS.Timeout; /** @@ -37,7 +44,10 @@ export class Client< * @param conn * @package */ - constructor(server: Server, conn: any) { + constructor( + server: Server, + conn: any + ) { this.server = server; this.conn = conn; this.encoder = server.encoder; @@ -98,7 +108,11 @@ export class Client< this.server._checkNamespace( name, auth, - (dynamicNspName: Namespace | false) => { + ( + dynamicNspName: + | Namespace + | false + ) => { if (dynamicNspName) { debug("dynamic namespace %s was created", dynamicNspName); this.doConnect(name, auth); @@ -156,7 +170,7 @@ export class Client< * * @private */ - _remove(socket: Socket): void { + _remove(socket: Socket): void { if (this.sockets.has(socket.id)) { const nsp = this.sockets.get(socket.id)!.nsp.name; this.sockets.delete(socket.id); diff --git a/lib/index.ts b/lib/index.ts index afda8ebd38..0962d1b6ea 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -26,6 +26,7 @@ import { DefaultEventsMap, EventParams, StrictEventEmitter, + EventNames, } from "./typed-events"; const debug = debugModule("socket.io:server"); @@ -170,13 +171,18 @@ interface ServerOptions extends EngineAttachOptions { export class Server< ListenEvents extends EventsMap = DefaultEventsMap, - EmitEvents extends EventsMap = ListenEvents + EmitEvents extends EventsMap = ListenEvents, + ServerSideEvents extends EventsMap = {} > extends StrictEventEmitter< - {}, + ServerSideEvents, EmitEvents, - NamespaceReservedEventsMap + NamespaceReservedEventsMap > { - public readonly sockets: Namespace; + public readonly sockets: Namespace< + ListenEvents, + EmitEvents, + ServerSideEvents + >; /** * A reference to the underlying Engine.IO server. * @@ -197,10 +203,13 @@ export class Server< /** * @private */ - _nsps: Map> = new Map(); + _nsps: Map< + string, + Namespace + > = new Map(); private parentNsps: Map< ParentNspNameMatchFn, - ParentNamespace + ParentNamespace > = new Map(); private _adapter?: typeof Adapter; private _serveClient: boolean; @@ -280,7 +289,9 @@ export class Server< _checkNamespace( name: string, auth: { [key: string]: any }, - fn: (nsp: Namespace | false) => void + fn: ( + nsp: Namespace | false + ) => void ): void { if (this.parentNsps.size === 0) return fn(false); @@ -589,8 +600,8 @@ export class Server< */ public of( name: string | RegExp | ParentNspNameMatchFn, - fn?: (socket: Socket) => void - ): Namespace { + fn?: (socket: Socket) => void + ): Namespace { if (typeof name === "function" || name instanceof RegExp) { const parentNsp = new ParentNamespace(this); debug("initializing parent namespace %s", parentNsp.name); @@ -649,7 +660,7 @@ export class Server< */ public use( fn: ( - socket: Socket, + socket: Socket, next: (err?: ExtendedError) => void ) => void ): this { @@ -686,7 +697,9 @@ export class Server< * @return self * @public */ - public except(name: Room | Room[]): Server { + public except( + name: Room | Room[] + ): Server { this.sockets.except(name); return this; } @@ -713,6 +726,20 @@ export class Server< return this; } + /** + * Emit a packet to other Socket.IO servers + * + * @param ev - the event name + * @param args - an array of arguments, which may include an acknowledgement callback at the end + * @public + */ + public serverSideEmit>( + ev: Ev, + ...args: EventParams + ): boolean { + return this.sockets.serverSideEmit(ev, ...args); + } + /** * Gets a list of socket ids. * diff --git a/lib/namespace.ts b/lib/namespace.ts index 5730a48f01..1eae5318fc 100644 --- a/lib/namespace.ts +++ b/lib/namespace.ts @@ -20,35 +20,43 @@ export interface ExtendedError extends Error { export interface NamespaceReservedEventsMap< ListenEvents extends EventsMap, - EmitEvents extends EventsMap + EmitEvents extends EventsMap, + ServerSideEvents extends EventsMap > { - connect: (socket: Socket) => void; - connection: (socket: Socket) => void; + connect: (socket: Socket) => void; + connection: ( + socket: Socket + ) => void; } +export const RESERVED_EVENTS: ReadonlySet = new Set< + keyof NamespaceReservedEventsMap +>(["connect", "connection"]); + export class Namespace< ListenEvents extends EventsMap = DefaultEventsMap, - EmitEvents extends EventsMap = ListenEvents + EmitEvents extends EventsMap = ListenEvents, + ServerSideEvents extends EventsMap = {} > extends StrictEventEmitter< - {}, + ServerSideEvents, EmitEvents, - NamespaceReservedEventsMap + NamespaceReservedEventsMap > { public readonly name: string; public readonly sockets: Map< SocketId, - Socket + Socket > = new Map(); public adapter: Adapter; /** @private */ - readonly server: Server; + readonly server: Server; /** @private */ _fns: Array< ( - socket: Socket, + socket: Socket, next: (err?: ExtendedError) => void ) => void > = []; @@ -62,7 +70,10 @@ export class Namespace< * @param server instance * @param name */ - constructor(server: Server, name: string) { + constructor( + server: Server, + name: string + ) { super(); this.server = server; this.name = name; @@ -88,7 +99,7 @@ export class Namespace< */ public use( fn: ( - socket: Socket, + socket: Socket, next: (err?: ExtendedError) => void ) => void ): this { @@ -104,7 +115,7 @@ export class Namespace< * @private */ private run( - socket: Socket, + socket: Socket, fn: (err: ExtendedError | null) => void ) { const fns = this._fns.slice(0); @@ -166,10 +177,10 @@ export class Namespace< * @private */ _add( - client: Client, + client: Client, query, fn?: () => void - ): Socket { + ): Socket { debug("adding socket to nsp %s", this.name); const socket = new Socket(this, client, query); this.run(socket, (err) => { @@ -212,7 +223,7 @@ export class Namespace< * * @private */ - _remove(socket: Socket): void { + _remove(socket: Socket): void { if (this.sockets.has(socket.id)) { this.sockets.delete(socket.id); } else { @@ -255,6 +266,37 @@ export class Namespace< return this; } + /** + * Emit a packet to other Socket.IO servers + * + * @param ev - the event name + * @param args - an array of arguments, which may include an acknowledgement callback at the end + * @public + */ + public serverSideEmit>( + ev: Ev, + ...args: EventParams + ): boolean { + if (RESERVED_EVENTS.has(ev)) { + throw new Error(`"${ev}" is a reserved event name`); + } + args.unshift(ev); + this.adapter.serverSideEmit(args); + return true; + } + + /** + * Called when a packet is received from another Socket.IO server + * + * @param args - an array of arguments, which may include an acknowledgement callback at the end + * + * @private + */ + _onServerSideEmit(args: any[]) { + const event = args.shift(); + this.emitUntyped(event, args); + } + /** * Gets a list of clients. * diff --git a/lib/parent-namespace.ts b/lib/parent-namespace.ts index 353d9bd812..2c09d8b728 100644 --- a/lib/parent-namespace.ts +++ b/lib/parent-namespace.ts @@ -10,12 +10,15 @@ import type { BroadcastOptions } from "socket.io-adapter"; export class ParentNamespace< ListenEvents extends EventsMap = DefaultEventsMap, - EmitEvents extends EventsMap = ListenEvents -> extends Namespace { + EmitEvents extends EventsMap = ListenEvents, + ServerSideEvents extends EventsMap = {} +> extends Namespace { private static count: number = 0; - private children: Set> = new Set(); + private children: Set< + Namespace + > = new Set(); - constructor(server: Server) { + constructor(server: Server) { super(server, "/_" + ParentNamespace.count++); } @@ -43,7 +46,9 @@ export class ParentNamespace< return true; } - createChild(name: string): Namespace { + createChild( + name: string + ): Namespace { const namespace = new Namespace(this.server, name); namespace._fns = this._fns.slice(0); this.listeners("connect").forEach((listener) => diff --git a/lib/socket.ts b/lib/socket.ts index 912274ccc8..40098d6901 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -46,7 +46,7 @@ export interface EventEmitterReservedEventsMap { export const RESERVED_EVENTS: ReadonlySet = new Set< | ClientReservedEvents - | keyof NamespaceReservedEventsMap + | keyof NamespaceReservedEventsMap | keyof SocketReservedEventsMap | keyof EventEmitterReservedEventsMap >([ @@ -110,7 +110,8 @@ export interface Handshake { export class Socket< ListenEvents extends EventsMap = DefaultEventsMap, - EmitEvents extends EventsMap = ListenEvents + EmitEvents extends EventsMap = ListenEvents, + ServerSideEvents extends EventsMap = {} > extends StrictEventEmitter< ListenEvents, EmitEvents, @@ -126,7 +127,7 @@ export class Socket< public connected: boolean; public disconnected: boolean; - private readonly server: Server; + private readonly server: Server; private readonly adapter: Adapter; private acks: Map void> = new Map(); private fns: Array< @@ -144,8 +145,8 @@ export class Socket< * @package */ constructor( - readonly nsp: Namespace, - readonly client: Client, + readonly nsp: Namespace, + readonly client: Client, auth: object ) { super(); diff --git a/test/socket.io.test-d.ts b/test/socket.io.test-d.ts index 22be55617a..deed33d62c 100644 --- a/test/socket.io.test-d.ts +++ b/test/socket.io.test-d.ts @@ -229,4 +229,48 @@ describe("server", () => { }); }); }); + + describe("listen and emit event maps", () => { + interface ClientToServerEvents { + helloFromClient: (message: string) => void; + } + + interface ServerToClientEvents { + helloFromServer: (message: string, x: number) => void; + } + + interface InterServerEvents { + helloFromServerToServer: (message: string, x: number) => void; + } + + describe("on", () => { + it("infers correct types for listener parameters", () => { + const srv = createServer(); + const sio = new Server< + ClientToServerEvents, + ServerToClientEvents, + InterServerEvents + >(srv); + + expectType< + Server + >(sio); + srv.listen(() => { + sio.serverSideEmit("helloFromServerToServer", "hello", 10); + sio + .of("/test") + .serverSideEmit("helloFromServerToServer", "hello", 10); + + sio.on("helloFromServerToServer", (message, x) => { + expectType(message); + expectType(x); + }); + sio.of("/test").on("helloFromServerToServer", (message, x) => { + expectType(message); + expectType(x); + }); + }); + }); + }); + }); }); From 499c89250d2db1ab7725ab2b74840e188c267c46 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Mon, 10 May 2021 23:47:33 +0200 Subject: [PATCH 3/5] feat: notify upon namespace creation A "new_namespace" event will be emitted when a new namespace is created: ```js io.on("new_namespace", (namespace) => { // ... }); ``` This could be used for example for registering the same middleware for each namespace. See https://github.com/socketio/socket.io/issues/3851 --- lib/index.ts | 19 ++++++++++++------- lib/namespace.ts | 18 ++++++++++++++++-- test/socket.io.ts | 31 ++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 0962d1b6ea..81259dad2e 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -7,11 +7,7 @@ import path = require("path"); import engine = require("engine.io"); import { Client } from "./client"; import { EventEmitter } from "events"; -import { - ExtendedError, - Namespace, - NamespaceReservedEventsMap, -} from "./namespace"; +import { ExtendedError, Namespace, ServerReservedEventsMap } from "./namespace"; import { ParentNamespace } from "./parent-namespace"; import { Adapter, Room, SocketId } from "socket.io-adapter"; import * as parser from "socket.io-parser"; @@ -176,7 +172,7 @@ export class Server< > extends StrictEventEmitter< ServerSideEvents, EmitEvents, - NamespaceReservedEventsMap + ServerReservedEventsMap > { public readonly sockets: Namespace< ListenEvents, @@ -306,7 +302,12 @@ export class Server< if (err || !allow) { run(); } else { - fn(this.parentNsps.get(nextFn.value)!.createChild(name)); + const namespace = this.parentNsps + .get(nextFn.value)! + .createChild(name); + // @ts-ignore + this.sockets.emitReserved("new_namespace", namespace); + fn(namespace); } }); }; @@ -627,6 +628,10 @@ export class Server< debug("initializing namespace %s", name); nsp = new Namespace(this, name); this._nsps.set(name, nsp); + if (name !== "/") { + // @ts-ignore + this.sockets.emitReserved("new_namespace", nsp); + } } if (fn) nsp.on("connect", fn); return nsp; diff --git a/lib/namespace.ts b/lib/namespace.ts index 1eae5318fc..4e472c1d4f 100644 --- a/lib/namespace.ts +++ b/lib/namespace.ts @@ -29,9 +29,23 @@ export interface NamespaceReservedEventsMap< ) => void; } +export interface ServerReservedEventsMap< + ListenEvents, + EmitEvents, + ServerSideEvents +> extends NamespaceReservedEventsMap< + ListenEvents, + EmitEvents, + ServerSideEvents + > { + new_namespace: ( + namespace: Namespace + ) => void; +} + export const RESERVED_EVENTS: ReadonlySet = new Set< - keyof NamespaceReservedEventsMap ->(["connect", "connection"]); + keyof ServerReservedEventsMap +>(["connect", "connection", "new_namespace"]); export class Namespace< ListenEvents extends EventsMap = DefaultEventsMap, diff --git a/test/socket.io.ts b/test/socket.io.ts index 84c57d1acb..88bf1adae3 100644 --- a/test/socket.io.ts +++ b/test/socket.io.ts @@ -812,7 +812,7 @@ describe("socket.io", () => { }); }); - it("should close a client without namespace", (done) => { + it("should close a client without namespace (2)", (done) => { const srv = createServer(); const sio = new Server(srv, { connectTimeout: 100, @@ -886,6 +886,17 @@ describe("socket.io", () => { }); }); + it("should emit an 'new_namespace' event", (done) => { + const sio = new Server(); + + sio.on("new_namespace", (namespace) => { + expect(namespace.name).to.eql("/nsp"); + done(); + }); + + sio.of("/nsp"); + }); + describe("dynamic namespaces", () => { it("should allow connections to dynamic namespaces with a regex", (done) => { const srv = createServer(); @@ -942,6 +953,24 @@ describe("socket.io", () => { }); }); }); + + it("should emit an 'new_namespace' event for a dynamic namespace", (done) => { + const srv = createServer(); + const sio = new Server(srv); + srv.listen(() => { + sio.of(/^\/dynamic-\d+$/); + + sio.on("new_namespace", (namespace) => { + expect(namespace.name).to.be("/dynamic-101"); + + socket.disconnect(); + srv.close(); + done(); + }); + + const socket = client(srv, "/dynamic-101"); + }); + }); }); }); From 95d9e4a42f136f6557cd969234510ecce6e49c49 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Mon, 10 May 2021 23:56:56 +0200 Subject: [PATCH 4/5] test: fix randomly failing test --- test/socket.io.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/socket.io.ts b/test/socket.io.ts index 88bf1adae3..206cf1d783 100644 --- a/test/socket.io.ts +++ b/test/socket.io.ts @@ -984,7 +984,9 @@ describe("socket.io", () => { clientSocket.off("connect", init); clientSocket.io.engine.close(); - clientSocket.connect(); + process.nextTick(() => { + clientSocket.connect(); + }); clientSocket.on("connect", () => { done(); }); From fb6b0efec9fcc154dc9131189d78fcfa0a6c7f46 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Tue, 11 May 2021 09:27:52 +0200 Subject: [PATCH 5/5] chore(release): 4.1.0 Diff: https://github.com/socketio/socket.io/compare/4.0.2...4.1.0 --- CHANGELOG.md | 16 + client-dist/socket.io.js | 364 ++++++++++++----------- client-dist/socket.io.js.map | 2 +- client-dist/socket.io.min.js | 4 +- client-dist/socket.io.min.js.map | 2 +- client-dist/socket.io.msgpack.min.js | 4 +- client-dist/socket.io.msgpack.min.js.map | 2 +- package-lock.json | 16 +- package.json | 4 +- 9 files changed, 224 insertions(+), 190 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49ade83d03..3ace70f89f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# [4.1.0](https://github.com/socketio/socket.io/compare/4.0.2...4.1.0) (2021-05-11) + + +### Features + +* add support for inter-server communication ([93cce05](https://github.com/socketio/socket.io/commit/93cce05fb3faf91f21fa71212275c776aa161107)) +* notify upon namespace creation ([499c892](https://github.com/socketio/socket.io/commit/499c89250d2db1ab7725ab2b74840e188c267c46)) +* add a "connection_error" event ([7096e98](https://github.com/socketio/engine.io/commit/7096e98a02295a62c8ea2aa56461d4875887092d), from `engine.io`) +* add the "initial_headers" and "headers" events ([2527543](https://github.com/socketio/engine.io/commit/252754353a0e88eb036ebb3082e9d6a9a5f497db), from `engine.io`) + + +### Performance Improvements + +* add support for the "wsPreEncoded" writing option ([dc381b7](https://github.com/socketio/socket.io/commit/dc381b72c6b2f8172001dedd84116122e4cc95b3)) + + ## [4.0.2](https://github.com/socketio/socket.io/compare/4.0.1...4.0.2) (2021-05-06) diff --git a/client-dist/socket.io.js b/client-dist/socket.io.js index 43e2c1d075..c05e62675e 100644 --- a/client-dist/socket.io.js +++ b/client-dist/socket.io.js @@ -1,5 +1,5 @@ /*! - * Socket.IO v4.0.2 + * Socket.IO v4.1.0 * (c) 2014-2021 Guillermo Rauch * Released under the MIT License. */ @@ -2571,7 +2571,8 @@ var Socket = /*#__PURE__*/function (_Emitter) { perMessageDeflate: { threshold: 1024 }, - transportOptions: {} + transportOptions: {}, + closeOnBeforeunload: true }, opts); _this.opts.path = _this.opts.path.replace(/\/$/, "") + "/"; @@ -2588,14 +2589,19 @@ var Socket = /*#__PURE__*/function (_Emitter) { _this.pingTimeoutTimer = null; if (typeof addEventListener === "function") { - addEventListener("beforeunload", function () { - if (_this.transport) { - // silently close the transport - _this.transport.removeAllListeners(); - - _this.transport.close(); - } - }, false); + if (_this.opts.closeOnBeforeunload) { + // Firefox closes the connection when the "beforeunload" event is emitted but not Chrome. This event listener + // ensures every browser behaves the same (no "disconnect" event at the Socket.IO level when the page is + // closed/reloaded) + addEventListener("beforeunload", function () { + if (_this.transport) { + // silently close the transport + _this.transport.removeAllListeners(); + + _this.transport.close(); + } + }, false); + } if (_this.hostname !== "localhost") { _this.offlineEventListener = function () { @@ -2651,15 +2657,16 @@ var Socket = /*#__PURE__*/function (_Emitter) { }, { key: "open", value: function open() { + var _this2 = this; + var transport; if (this.opts.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf("websocket") !== -1) { transport = "websocket"; } else if (0 === this.transports.length) { // Emit error on next tick so it can be listened to - var self = this; setTimeout(function () { - self.emit("error", "No transports available"); + _this2.emit("error", "No transports available"); }, 0); return; } else { @@ -2689,8 +2696,9 @@ var Socket = /*#__PURE__*/function (_Emitter) { }, { key: "setTransport", value: function setTransport(transport) { + var _this3 = this; + debug("setting transport %s", transport.name); - var self = this; if (this.transport) { debug("clearing existing transport %s", this.transport.name); @@ -2700,14 +2708,8 @@ var Socket = /*#__PURE__*/function (_Emitter) { this.transport = transport; // set up transport listeners - transport.on("drain", function () { - self.onDrain(); - }).on("packet", function (packet) { - self.onPacket(packet); - }).on("error", function (e) { - self.onError(e); - }).on("close", function () { - self.onClose("transport close"); + transport.on("drain", this.onDrain.bind(this)).on("packet", this.onPacket.bind(this)).on("error", this.onError.bind(this)).on("close", function () { + _this3.onClose("transport close"); }); } /** @@ -2720,20 +2722,16 @@ var Socket = /*#__PURE__*/function (_Emitter) { }, { key: "probe", value: function probe(name) { + var _this4 = this; + debug('probing transport "%s"', name); var transport = this.createTransport(name, { probe: 1 }); var failed = false; - var self = this; Socket.priorWebsocketSuccess = false; - function onTransportOpen() { - if (self.onlyBinaryUpgrades) { - var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; - failed = failed || upgradeLosesBinary; - } - + var onTransportOpen = function onTransportOpen() { if (failed) return; debug('probe transport "%s" opened', name); transport.send([{ @@ -2745,33 +2743,42 @@ var Socket = /*#__PURE__*/function (_Emitter) { if ("pong" === msg.type && "probe" === msg.data) { debug('probe transport "%s" pong', name); - self.upgrading = true; - self.emit("upgrading", transport); + _this4.upgrading = true; + + _this4.emit("upgrading", transport); + if (!transport) return; Socket.priorWebsocketSuccess = "websocket" === transport.name; - debug('pausing current transport "%s"', self.transport.name); - self.transport.pause(function () { + debug('pausing current transport "%s"', _this4.transport.name); + + _this4.transport.pause(function () { if (failed) return; - if ("closed" === self.readyState) return; + if ("closed" === _this4.readyState) return; debug("changing transport and sending upgrade packet"); cleanup(); - self.setTransport(transport); + + _this4.setTransport(transport); + transport.send([{ type: "upgrade" }]); - self.emit("upgrade", transport); + + _this4.emit("upgrade", transport); + transport = null; - self.upgrading = false; - self.flush(); + _this4.upgrading = false; + + _this4.flush(); }); } else { debug('probe transport "%s" failed', name); var err = new Error("probe error"); err.transport = transport.name; - self.emit("upgradeError", err); + + _this4.emit("upgradeError", err); } }); - } + }; function freezeTransport() { if (failed) return; // Any callback called by transport should be ignored since now @@ -2783,13 +2790,14 @@ var Socket = /*#__PURE__*/function (_Emitter) { } // Handle any error that happens while probing - function onerror(err) { + var onerror = function onerror(err) { var error = new Error("probe error: " + err); error.transport = transport.name; freezeTransport(); debug('probe transport "%s" failed because of error: %s', name, err); - self.emit("upgradeError", error); - } + + _this4.emit("upgradeError", error); + }; function onTransportClose() { onerror("transport closed"); @@ -2809,13 +2817,15 @@ var Socket = /*#__PURE__*/function (_Emitter) { } // Remove all listeners on the transport and on self - function cleanup() { + var cleanup = function cleanup() { transport.removeListener("open", onTransportOpen); transport.removeListener("error", onerror); transport.removeListener("close", onTransportClose); - self.removeListener("close", onclose); - self.removeListener("upgrading", onupgrade); - } + + _this4.removeListener("close", onclose); + + _this4.removeListener("upgrading", onupgrade); + }; transport.once("open", onTransportOpen); transport.once("error", onerror); @@ -2921,11 +2931,11 @@ var Socket = /*#__PURE__*/function (_Emitter) { }, { key: "resetPingTimeout", value: function resetPingTimeout() { - var _this2 = this; + var _this5 = this; clearTimeout(this.pingTimeoutTimer); this.pingTimeoutTimer = setTimeout(function () { - _this2.onClose("ping timeout"); + _this5.onClose("ping timeout"); }, this.pingInterval + this.pingTimeout); if (this.opts.autoUnref) { @@ -3041,14 +3051,37 @@ var Socket = /*#__PURE__*/function (_Emitter) { }, { key: "close", value: function close() { - var self = this; + var _this6 = this; + + var close = function close() { + _this6.onClose("forced close"); + + debug("socket closing - telling transport to close"); + + _this6.transport.close(); + }; + + var cleanupAndClose = function cleanupAndClose() { + _this6.removeListener("upgrade", cleanupAndClose); + + _this6.removeListener("upgradeError", cleanupAndClose); + + close(); + }; + + var waitForUpgrade = function waitForUpgrade() { + // wait for upgrade to finish since we can't send packets while pausing a transport + _this6.once("upgrade", cleanupAndClose); + + _this6.once("upgradeError", cleanupAndClose); + }; if ("opening" === this.readyState || "open" === this.readyState) { this.readyState = "closing"; if (this.writeBuffer.length) { this.once("drain", function () { - if (this.upgrading) { + if (_this6.upgrading) { waitForUpgrade(); } else { close(); @@ -3061,24 +3094,6 @@ var Socket = /*#__PURE__*/function (_Emitter) { } } - function close() { - self.onClose("forced close"); - debug("socket closing - telling transport to close"); - self.transport.close(); - } - - function cleanupAndClose() { - self.removeListener("upgrade", cleanupAndClose); - self.removeListener("upgradeError", cleanupAndClose); - close(); - } - - function waitForUpgrade() { - // wait for upgrade to finish since we can't send packets while pausing a transport - self.once("upgrade", cleanupAndClose); - self.once("upgradeError", cleanupAndClose); - } - return this; } /** @@ -3105,8 +3120,7 @@ var Socket = /*#__PURE__*/function (_Emitter) { key: "onClose", value: function onClose(reason, desc) { if ("opening" === this.readyState || "open" === this.readyState || "closing" === this.readyState) { - debug('socket close with reason: "%s"', reason); - var self = this; // clear timers + debug('socket close with reason: "%s"', reason); // clear timers clearTimeout(this.pingIntervalTimer); clearTimeout(this.pingTimeoutTimer); // stop event from firing again for transport @@ -3129,8 +3143,8 @@ var Socket = /*#__PURE__*/function (_Emitter) { this.emit("close", reason, desc); // clean buffers after, so users can still // grab the buffers on `close` event - self.writeBuffer = []; - self.prevBufferLen = 0; + this.writeBuffer = []; + this.prevBufferLen = 0; } } /** @@ -3494,11 +3508,7 @@ var JSONPPolling = /*#__PURE__*/function (_Polling) { _this.index = callbacks.length; // add callback to jsonp global - var self = _assertThisInitialized(_this); - - callbacks.push(function (msg) { - self.onData(msg); - }); // append to query string + callbacks.push(_this.onData.bind(_assertThisInitialized(_this))); // append to query string _this.query.j = _this.index; return _this; @@ -3542,7 +3552,8 @@ var JSONPPolling = /*#__PURE__*/function (_Polling) { }, { key: "doPoll", value: function doPoll() { - var self = this; + var _this2 = this; + var script = document.createElement("script"); if (this.script) { @@ -3554,7 +3565,7 @@ var JSONPPolling = /*#__PURE__*/function (_Polling) { script.src = this.uri(); script.onerror = function (e) { - self.onError("jsonp poll error", e); + _this2.onError("jsonp poll error", e); }; var insertAt = document.getElementsByTagName("script")[0]; @@ -3587,7 +3598,8 @@ var JSONPPolling = /*#__PURE__*/function (_Polling) { }, { key: "doWrite", value: function doWrite(data, fn) { - var self = this; + var _this3 = this; + var iframe; if (!this.form) { @@ -3615,29 +3627,31 @@ var JSONPPolling = /*#__PURE__*/function (_Polling) { fn(); } - function initIframe() { - if (self.iframe) { + var initIframe = function initIframe() { + if (_this3.iframe) { try { - self.form.removeChild(self.iframe); + _this3.form.removeChild(_this3.iframe); } catch (e) { - self.onError("jsonp polling iframe removal error", e); + _this3.onError("jsonp polling iframe removal error", e); } } try { // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) - var html = '