Skip to content

Commit f0ed42f

Browse files
feat: add timeout feature
Usage: ```js socket.timeout(5000).emit("my-event", (err) => { if (err) { // the client did not acknowledge the event in the given delay } }); ```
1 parent b7213e7 commit f0ed42f

File tree

4 files changed

+107
-4
lines changed

4 files changed

+107
-4
lines changed

Diff for: lib/socket.ts

+47-4
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export class Socket<
137137
private readonly adapter: Adapter;
138138
private acks: Map<number, () => void> = new Map();
139139
private fns: Array<(event: Event, next: (err?: Error) => void) => void> = [];
140-
private flags: BroadcastFlags = {};
140+
private flags: BroadcastFlags & { timeout?: number } = {};
141141
private _anyListeners?: Array<(...args: any[]) => void>;
142142

143143
/**
@@ -207,9 +207,11 @@ export class Socket<
207207

208208
// access last argument to see if it's an ACK callback
209209
if (typeof data[data.length - 1] === "function") {
210-
debug("emitting packet with ack id %d", this.nsp._ids);
211-
this.acks.set(this.nsp._ids, data.pop());
212-
packet.id = this.nsp._ids++;
210+
const id = this.nsp._ids++;
211+
debug("emitting packet with ack id %d", id);
212+
213+
this.registerAckCallback(id, data.pop());
214+
packet.id = id;
213215
}
214216

215217
const flags = Object.assign({}, this.flags);
@@ -220,6 +222,27 @@ export class Socket<
220222
return true;
221223
}
222224

225+
/**
226+
* @private
227+
*/
228+
private registerAckCallback(id: number, ack: (...args: any[]) => void): void {
229+
const timeout = this.flags.timeout;
230+
if (timeout === undefined) {
231+
this.acks.set(id, ack);
232+
return;
233+
}
234+
235+
const timer = setTimeout(() => {
236+
debug("event with ack id %d has timed out after %d ms", id, timeout);
237+
ack.call(this, new Error("operation has timed out"));
238+
}, timeout);
239+
240+
this.acks.set(id, (...args) => {
241+
clearTimeout(timer);
242+
ack.apply(this, [null, ...args]);
243+
});
244+
}
245+
223246
/**
224247
* Targets a room when broadcasting.
225248
*
@@ -567,6 +590,26 @@ export class Socket<
567590
return this.newBroadcastOperator().local;
568591
}
569592

593+
/**
594+
* Sets a modifier for a subsequent event emission that the callback will be called with an error when the
595+
* given number of milliseconds have elapsed without an acknowledgement from the client:
596+
*
597+
* ```
598+
* socket.timeout(5000).emit("my-event", (err) => {
599+
* if (err) {
600+
* // the client did not acknowledge the event in the given delay
601+
* }
602+
* });
603+
* ```
604+
*
605+
* @returns self
606+
* @public
607+
*/
608+
public timeout(timeout: number): this {
609+
this.flags.timeout = timeout;
610+
return this;
611+
}
612+
570613
/**
571614
* Dispatch incoming event to socket listeners.
572615
*

Diff for: test/socket-timeout.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Server } from "..";
2+
import { createClient, success } from "./support/util";
3+
import expect from "expect.js";
4+
5+
describe("timeout", () => {
6+
it("should timeout if the client does not acknowledge the event", (done) => {
7+
const io = new Server(0);
8+
const client = createClient(io, "/");
9+
10+
io.on("connection", (socket) => {
11+
socket.timeout(50).emit("unknown", (err) => {
12+
expect(err).to.be.an(Error);
13+
success(done, io, client);
14+
});
15+
});
16+
});
17+
18+
it("should not timeout if the client does acknowledge the event", (done) => {
19+
const io = new Server(0);
20+
const client = createClient(io, "/");
21+
22+
client.on("echo", (arg, cb) => {
23+
cb(arg);
24+
});
25+
26+
io.on("connection", (socket) => {
27+
socket.timeout(50).emit("echo", 42, (err, value) => {
28+
expect(err).to.be(null);
29+
expect(value).to.be(42);
30+
success(done, io, client);
31+
});
32+
});
33+
});
34+
});

Diff for: test/socket.io.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2888,4 +2888,6 @@ describe("socket.io", () => {
28882888
});
28892889
});
28902890
});
2891+
2892+
require("./socket-timeout");
28912893
});

Diff for: test/support/util.ts

+24
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import type { Server } from "../..";
2+
import {
3+
io as ioc,
4+
ManagerOptions,
5+
Socket as ClientSocket,
6+
SocketOptions,
7+
} from "socket.io-client";
8+
19
const expect = require("expect.js");
210
const i = expect.stringify;
311

@@ -20,3 +28,19 @@ expect.Assertion.prototype.contain = function (...args) {
2028
}
2129
return contain.apply(this, args);
2230
};
31+
32+
export function createClient(
33+
io: Server,
34+
nsp: string,
35+
opts?: ManagerOptions & SocketOptions
36+
): ClientSocket {
37+
// @ts-ignore
38+
const port = io.httpServer.address().port;
39+
return ioc(`http://localhost:${port}${nsp}`, opts);
40+
}
41+
42+
export function success(done: Function, io: Server, client: ClientSocket) {
43+
io.close();
44+
client.disconnect();
45+
done();
46+
}

0 commit comments

Comments
 (0)