Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions packages/socket.io/lib/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ import {

const debug = debugModule("socket.io:socket");

function hasBinary(data: unknown): boolean {
if (!data || typeof data !== "object") {
return false;
}
if (
Buffer.isBuffer(data) ||
data instanceof ArrayBuffer ||
ArrayBuffer.isView(data)
) {
return true;
}
if (Array.isArray(data)) {
return data.some(hasBinary);
}
return Object.values(data).some(hasBinary);
}

const RECOVERABLE_DISCONNECT_REASONS: ReadonlySet<DisconnectReason> = new Set([
"transport error",
"transport close",
Expand Down Expand Up @@ -571,7 +588,10 @@ export class Socket<
listener.apply(this, args);
}
}
this.dispatch(args);
this.dispatch(
args,
packet.type === PacketType.BINARY_EVENT || hasBinary(args),
);
}

/**
Expand Down Expand Up @@ -817,10 +837,12 @@ export class Socket<
* @param {Array} event - event that will get emitted
* @private
*/
private dispatch(event: Event): void {
private dispatch(event: Event, isBinary: boolean): void {
debug("dispatching an event %j", event);
let isSynchronous = true;

this.run(event, (err) => {
process.nextTick(() => {
const invoke = () => {
if (err) {
return this._onerror(err);
}
Expand All @@ -829,8 +851,16 @@ export class Socket<
} else {
debug("ignore packet received after disconnection");
}
});
};

if (isSynchronous && !isBinary && !err) {
invoke();
} else {
process.nextTick(invoke);
}
});

isSynchronous = false;
}

/**
Expand Down
141 changes: 141 additions & 0 deletions packages/socket.io/test/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "./support/util";
import { Server } from "..";
import expect from "expect.js";
import { PacketType } from "socket.io-parser";

describe("socket", () => {
it("should not fire events more than once after manually reconnecting", (done) => {
Expand Down Expand Up @@ -907,6 +908,146 @@ describe("socket", () => {
});
});

it("should dispatch a non-binary event before disconnect cleanup", (done) => {
const io = new Server(0);
const clientSocket = createClient(io);

io.on("connection", (socket) => {
let anyCalled = false;
let namedCalled = false;

socket.onAny((event) => {
if (event === "before-disconnect") {
anyCalled = true;
}
});
socket.on("before-disconnect", () => {
namedCalled = true;
});

(socket as any).onevent({
type: PacketType.EVENT,
data: ["before-disconnect"],
});
(socket as any).ondisconnect();

expect(anyCalled).to.be(true);
expect(namedCalled).to.be(true);
success(done, io, clientSocket);
});
});

it("should dispatch a non-binary event after synchronous middleware", (done) => {
const io = new Server(0);
const clientSocket = createClient(io);

io.on("connection", (socket) => {
let namedCalled = false;

socket.use((_event, next) => {
next();
});
socket.on("before-disconnect", () => {
namedCalled = true;
});

(socket as any).onevent({
type: PacketType.EVENT,
data: ["before-disconnect"],
});
(socket as any).ondisconnect();

expect(namedCalled).to.be(true);
success(done, io, clientSocket);
});
});

it("should ignore an event when middleware completes after disconnect", (done) => {
const io = new Server(0);
const clientSocket = createClient(io);

io.on("connection", (socket) => {
let namedCalled = false;

socket.use((_event, next) => {
setImmediate(next);
});
socket.on("before-disconnect", () => {
namedCalled = true;
});

(socket as any).onevent({
type: PacketType.EVENT,
data: ["before-disconnect"],
});
(socket as any).ondisconnect();

setImmediate(() => {
expect(namedCalled).to.be(false);
success(done, io, clientSocket);
});
});
});

it("should ignore an event received after disconnection", (done) => {
const io = new Server(0);
const clientSocket = createClient(io);

io.on("connection", (socket) => {
let namedCalled = false;

socket.on("before-disconnect", () => {
namedCalled = true;
});

(socket as any).ondisconnect();
(socket as any).onevent({
type: PacketType.EVENT,
data: ["before-disconnect"],
});

setImmediate(() => {
expect(namedCalled).to.be(false);
success(done, io, clientSocket);
});
});
});

it("should dispatch non-binary events before disconnect cleanup for many clients", (done) => {
const io = new Server(0);
const clients = [];
let anyCalled = 0;
let namedCalled = 0;
let disconnected = 0;

io.on("connection", (socket) => {
socket.onAny((event) => {
if (event === "before-disconnect") {
anyCalled++;
}
});
socket.on("before-disconnect", () => {
namedCalled++;
});
socket.on("disconnect", () => {
if (++disconnected === 40) {
expect(anyCalled).to.be(40);
expect(namedCalled).to.be(40);
success(done, io, ...clients);
}
});
});

for (let i = 0; i < 40; i++) {
const clientSocket = createClient(io);
clients.push(clientSocket);
clientSocket.on("connect", () => {
clientSocket.emit("before-disconnect");
clientSocket.disconnect();
});
}
});

it("should leave all rooms joined after a middleware failure", (done) => {
const io = new Server(0);
const client = createClient(io, "/");
Expand Down