Skip to content

Commit 33e4172

Browse files
enginadarrachequesne
authored andcommitted
fix(typings): properly type emits with timeout (#1570)
When emitting with a timeout (added in version 4.4.0), the "err" argument was not properly typed and would require to split the client and server typings. It will now be automatically inferred as an Error object. Workaround for previous versions: ```ts type WithTimeoutAck<isEmitter extends boolean, args extends any[]> = isEmitter extends true ? [Error, ...args] : args; interface ClientToServerEvents<isEmitter extends boolean = false> { withAck: (data: { argName: boolean }, callback: (...args: WithTimeoutAck<isEmitter, [string]>) => void) => void; } interface ServerToClientEvents<isEmitter extends boolean = false> { } const io = new Server<ClientToServerEvents, ServerToClientEvents<true>>(3000); io.on("connection", (socket) => { socket.on("withAck", (val, cb) => { cb("123"); }); }); const socket: Socket<ServerToClientEvents, ClientToServerEvents<true>> = ioc("http://localhost:3000"); socket.timeout(100).emit("withAck", { argName: true }, (err, val) => { // ... }); ``` Related: #1555
1 parent 18c6e0a commit 33e4172

File tree

2 files changed

+71
-1
lines changed

2 files changed

+71
-1
lines changed

Diff for: lib/socket.ts

+37-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,40 @@ import debugModule from "debug"; // debug()
1212

1313
const debug = debugModule("socket.io-client:socket"); // debug()
1414

15+
type Last<T extends any[]> = T extends [...infer I, infer L] ? L : any;
16+
type AllButLast<T extends any[]> = T extends [...infer I, infer L] ? I : any[];
17+
18+
type PrependTimeoutError<T> = T extends (...args: infer Params) => infer Result
19+
? (err: Error, ...args: Params) => Result
20+
: T;
21+
22+
/**
23+
* Utility type to decorate the acknowledgement callbacks with a timeout error.
24+
*
25+
* This is needed because the timeout() flag breaks the symmetry between the sender and the receiver:
26+
*
27+
* @example
28+
* interface Events {
29+
* "my-event": (val: string) => void;
30+
* }
31+
*
32+
* socket.on("my-event", (cb) => {
33+
* cb("123"); // one single argument here
34+
* });
35+
*
36+
* socket.timeout(1000).emit("my-event", (err, val) => {
37+
* // two arguments there (the "err" argument is not properly typed)
38+
* });
39+
*
40+
*/
41+
export type DecorateAcknowledgements<E> = {
42+
[K in keyof E]: E[K] extends (...args: infer Params) => infer Result
43+
? (
44+
...args: [...AllButLast<Params>, PrependTimeoutError<Last<Params>>]
45+
) => Result
46+
: E[K];
47+
};
48+
1549
export interface SocketOptions {
1650
/**
1751
* the authentication payload sent when connecting to the Namespace
@@ -677,7 +711,9 @@ export class Socket<
677711
*
678712
* @returns self
679713
*/
680-
public timeout(timeout: number): this {
714+
public timeout(
715+
timeout: number
716+
): Socket<ListenEvents, DecorateAcknowledgements<EmitEvents>> {
681717
this.flags.timeout = timeout;
682718
return this;
683719
}

Diff for: test/typed-events.test-d.ts

+34
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ describe("typed events", () => {
5959

6060
socket.emit("random", 1, "2", [3]);
6161
socket.emit("no parameters");
62+
63+
socket.emit("ackFromClient", "1", 2, (c, d) => {
64+
expectType<any>(c);
65+
expectType<any>(d);
66+
});
67+
68+
socket.timeout(1000).emit("ackFromClient", "1", 2, (err, c, d) => {
69+
expectType<any>(err);
70+
expectType<any>(c);
71+
expectType<any>(d);
72+
});
6273
});
6374
});
6475
});
@@ -111,6 +122,12 @@ describe("typed events", () => {
111122
describe("listen and emit event maps", () => {
112123
interface ClientToServerEvents {
113124
helloFromClient: (message: string) => void;
125+
ackFromClient: (
126+
a: string,
127+
b: number,
128+
ack: (c: string, d: boolean) => void
129+
) => void;
130+
ackFromClientNoArg: (ack: () => void) => void;
114131
}
115132

116133
interface ServerToClientEvents {
@@ -142,6 +159,23 @@ describe("typed events", () => {
142159
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io();
143160

144161
socket.emit("helloFromClient", "hi");
162+
163+
socket.emit("ackFromClient", "1", 2, (c, d) => {
164+
expectType<string>(c);
165+
expectType<boolean>(d);
166+
});
167+
168+
socket.timeout(1000).emit("ackFromClient", "1", 2, (err, c, d) => {
169+
expectType<Error>(err);
170+
expectType<string>(c);
171+
expectType<boolean>(d);
172+
});
173+
174+
socket.emit("ackFromClientNoArg", () => {});
175+
176+
socket.timeout(1000).emit("ackFromClientNoArg", (err) => {
177+
expectType<Error>(err);
178+
});
145179
});
146180

147181
it("does not accept arguments of wrong types", () => {

0 commit comments

Comments
 (0)