Description
A connection attempt can leave observable state or complete after it has already been abandoned.
Two exit paths share this lifecycle problem:
- Middleware may join the temporary server socket to a room and then reject the connection. Smocket reports
connect_error, but the temporary socket's room and adapter membership can remain.
- A client may call
disconnect() while middleware is still pending. Smocket currently treats the pre-connect disconnect as a no-op, so a later next() can still admit the socket and call the connection handler.
Socket.IO 4.8.3 probes established the expected logical outcomes for these cases: a rejected attempt does not survive in namespace membership, and a cancelled pending attempt is not admitted when its middleware later completes. Transport internals are not part of the claim. The event sequence, fresh-attempt behavior, and shared result must be verified against Socket.IO 4.7 and 4.8 before implementation is considered complete.
The fix should give each attempt an explicit terminal state and make cleanup idempotent. Rejection or cancellation must remove temporary rooms and adapter membership, keep the socket out of the namespace roster and ready queue, and prevent a late middleware callback from connecting it.
Reproduction
import { connect, Server } from 'smocket';
const URL = 'http://localhost:3000';
const io = new Server(URL);
io.use(async (socket, next) => {
await socket.join('temporary');
next(new Error('denied'));
});
const rejected = connect(URL);
rejected.on('connect_error', () => {
// Expected: no temporary sid or room membership remains.
console.log(io.of('/').adapter.rooms);
console.log(io.of('/').adapter.sids);
});
const pendingIo = new Server('http://localhost:3001');
let continueMiddleware!: () => void;
pendingIo.use((_socket, next) => {
continueMiddleware = next;
});
let connections = 0;
pendingIo.on('connection', () => connections++);
const cancelled = connect('http://localhost:3001');
cancelled.disconnect();
continueMiddleware();
// Expected after the queued work settles: false, no id, and no connection handler.
console.log(cancelled.connected, cancelled.id, connections);
Completion tests must prove non-admission through ordering or a later marker, not by waiting for a timeout. They must not invent disconnect events or errors that have not been observed on the supported Socket.IO versions.
smocket version
0.4.2
Environment
Node 22, Vitest 4; Socket.IO 4.8.3 reference probes, with Socket.IO 4.7 and 4.8 verification required
Description
A connection attempt can leave observable state or complete after it has already been abandoned.
Two exit paths share this lifecycle problem:
connect_error, but the temporary socket's room and adapter membership can remain.disconnect()while middleware is still pending. Smocket currently treats the pre-connect disconnect as a no-op, so a laternext()can still admit the socket and call the connection handler.Socket.IO 4.8.3 probes established the expected logical outcomes for these cases: a rejected attempt does not survive in namespace membership, and a cancelled pending attempt is not admitted when its middleware later completes. Transport internals are not part of the claim. The event sequence, fresh-attempt behavior, and shared result must be verified against Socket.IO 4.7 and 4.8 before implementation is considered complete.
The fix should give each attempt an explicit terminal state and make cleanup idempotent. Rejection or cancellation must remove temporary rooms and adapter membership, keep the socket out of the namespace roster and ready queue, and prevent a late middleware callback from connecting it.
Reproduction
Completion tests must prove non-admission through ordering or a later marker, not by waiting for a timeout. They must not invent disconnect events or errors that have not been observed on the supported Socket.IO versions.
smocket version
0.4.2
Environment
Node 22, Vitest 4; Socket.IO 4.8.3 reference probes, with Socket.IO 4.7 and 4.8 verification required