Skip to content

Commit

Permalink
Remove events with acks from the server and namespace emits
Browse files Browse the repository at this point in the history
  • Loading branch information
ZachHaber committed Oct 10, 2023
1 parent de96dc9 commit 6cf63e9
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 38 deletions.
8 changes: 6 additions & 2 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,9 @@ export class Server<
* @return self
*/
public send(...args: EventParams<EmitEvents, "message">): this {
this.sockets.emit("message", ...args);
// This type-cast is needed because EmitEvents likely doesn't have `message` as a key.
// if you specify the EmitEvents, the type of args will be never.
this.sockets.emit("message" as any, ...args);
return this;
}

Expand All @@ -873,7 +875,9 @@ export class Server<
* @return self
*/
public write(...args: EventParams<EmitEvents, "message">): this {
this.sockets.emit("message", ...args);
// This type-cast is needed because EmitEvents likely doesn't have `message` as a key.
// if you specify the EmitEvents, the type of args will be never.
this.sockets.emit("message" as any, ...args);
return this;
}

Expand Down
13 changes: 9 additions & 4 deletions lib/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
RemoveAcknowledgements,
EventNamesWithAck,
FirstNonErrorArg,
EventNamesWithoutAck,
} from "./typed-events";
import type { Client } from "./client";
import debugModule from "debug";
Expand Down Expand Up @@ -440,9 +441,9 @@ export class Namespace<
*
* @return Always true
*/
public emit<Ev extends EventNames<EmitEvents>>(
public emit<Ev extends EventNamesWithoutAck<EmitEvents>>(
ev: Ev,
...args: EventParams<RemoveAcknowledgements<EmitEvents>, Ev>
...args: EventParams<EmitEvents, Ev>
): boolean {
return new BroadcastOperator<EmitEvents, SocketData>(this.adapter).emit(
ev,
Expand All @@ -468,7 +469,9 @@ export class Namespace<
* @return self
*/
public send(...args: EventParams<EmitEvents, "message">): this {
this.emit("message", ...args);
// This type-cast is needed because EmitEvents likely doesn't have `message` as a key.
// if you specify the EmitEvents, the type of args will be never.
this.emit("message" as any, ...args);
return this;
}

Expand All @@ -478,7 +481,9 @@ export class Namespace<
* @return self
*/
public write(...args: EventParams<EmitEvents, "message">): this {
this.emit("message", ...args);
// This type-cast is needed because EmitEvents likely doesn't have `message` as a key.
// if you specify the EmitEvents, the type of args will be never.
this.emit("message" as any, ...args);
return this;
}

Expand Down
4 changes: 2 additions & 2 deletions lib/parent-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Namespace } from "./namespace";
import type { Server, RemoteSocket } from "./index";
import type {
EventParams,
EventNames,
EventsMap,
DefaultEventsMap,
EventNamesWithoutAck,
} from "./typed-events";
import type { BroadcastOptions } from "socket.io-adapter";
import debugModule from "debug";
Expand Down Expand Up @@ -56,7 +56,7 @@ export class ParentNamespace<
this.adapter = { broadcast };
}

public emit<Ev extends EventNames<EmitEvents>>(
public emit<Ev extends EventNamesWithoutAck<EmitEvents>>(
ev: Ev,
...args: EventParams<EmitEvents, Ev>
): boolean {
Expand Down
29 changes: 21 additions & 8 deletions lib/typed-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@ export type EventNamesWithAck<
? K
: never
>;
/**
* Returns a union type containing all the keys of an event map that have an acknowledgement callback.
*
* That also have *some* data coming in.
*/
export type EventNamesWithoutAck<
Map extends EventsMap,
K extends EventNames<Map> = EventNames<Map>
> = IfAny<
Last<Parameters<Map[K]>> | Map[K],
K,
K extends (
Last<Parameters<Map[K]>> extends (...args: any[]) => any ? never : K
)
? K
: never
>;

export type RemoveAcknowledgements<E extends EventsMap> = {
[K in EventNamesWithoutAck<E>]: E[K];
};

export type EventNamesWithError<
Map extends EventsMap,
Expand Down Expand Up @@ -338,11 +359,3 @@ export type DecorateAcknowledgementsWithMultipleResponses<E> = {
? (...args: ExpectMultipleResponses<Params>) => Result
: E[K];
};

export type RemoveAcknowledgements<E> = {
[K in keyof E]: E[K] extends (...args: infer Params) => infer Result
? Last<Params> extends (...args: any[]) => any
? (...args: AllButLast<Params>) => Result
: E[K]
: E[K];
};
88 changes: 66 additions & 22 deletions test/socket.io.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { Adapter } from "socket.io-adapter";
import { expectType } from "tsd";
import { BroadcastOperator, Server, Socket } from "../lib/index";
import type { DisconnectReason } from "../lib/socket";
import type { DefaultEventsMap, EventsMap } from "../lib/typed-events";
import type {
DefaultEventsMap,
EventNamesWithoutAck,
EventsMap,
} from "../lib/typed-events";

// This file is run by tsd, not mocha.

Expand Down Expand Up @@ -93,6 +97,17 @@ describe("server", () => {
});
});
});
describe("send", () => {
it("accepts any parameters", () => {
const srv = createServer();
const sio = new Server(srv);
const nio = sio.of("/test");
sio.send(1, "2", [3]);
sio.send();
nio.send(1, "2", [3]);
nio.send();
});
});

describe("emitWithAck", () => {
it("accepts any parameters", () => {
Expand Down Expand Up @@ -198,9 +213,9 @@ describe("server", () => {
// it's likely better to just write them out, so that both the types and this are tested properly
interface ServerToClientEventsNoAck {
helloFromServer: (message: string, x: number) => void;
ackFromServer: (a: boolean, b: string) => void;
ackFromServerSingleArg: (a: boolean, b: string) => void;
onlyCallback: () => void;
ackFromServer: never;
ackFromServerSingleArg: never;
onlyCallback: never;
}
interface ServerToClientEventsWithError {
helloFromServer: (message: string, x: number) => void;
Expand Down Expand Up @@ -256,6 +271,36 @@ describe("server", () => {
onlyCallback: () => Promise<undefined>;
}
describe("Emitting Types", () => {
describe("send", () => {
it("prevents arguments if EmitEvents doesn't have message", () => {
const sio = new Server<ClientToServerEvents, ServerToClientEvents>();
const nio = sio.of("/test");
// @ts-expect-error - ServerToClientEvents doesn't have a message event
sio.send(1, "2", [3]);
// @ts-expect-error - ServerToClientEvents doesn't have a message event
nio.send(1, "2", [3]);
// This should likely be an error, but I don't know how to make it one
sio.send();
nio.send();
});
it("has the correct types", () => {
const sio = new Server<
{},
{ message: (a: number, b: string, c: number[]) => void }
>();
const nio = sio.of("/test");
sio.send(1, "2", [3]);
nio.send(1, "2", [3]);
// @ts-expect-error - message requires arguments
sio.send();
// @ts-expect-error - message requires arguments
nio.send();
// @ts-expect-error - message requires the correct arguments
sio.send(1, 2, [3]);
// @ts-expect-error - message requires the correct arguments
nio.send(1, 2, [3]);
});
});
describe("Broadcast Operator", () => {
it("works untyped", () => {
const untyped = new Server();
Expand Down Expand Up @@ -383,33 +428,16 @@ describe("server", () => {
it("Infers correct types", () => {
const sio = new Server<ClientToServerEvents, ServerToClientEvents>();
const nio = sio.of("/test");

expectType<ToEmit<ServerToClientEventsNoAck, "helloFromServer">>(
// These errors will dissapear once the TS version is updated from 4.7.4
// the TSD instance is using a newer version of TS than the workspace version
// to enable the ability to compare against `any`
sio.emit<"helloFromServer">
);
expectType<ToEmit<ServerToClientEventsNoAck, "ackFromServerSingleArg">>(
sio.emit<"ackFromServerSingleArg">
);
expectType<ToEmit<ServerToClientEventsNoAck, "ackFromServer">>(
sio.emit<"ackFromServer">
);
expectType<ToEmit<ServerToClientEventsNoAck, "onlyCallback">>(
sio.emit<"onlyCallback">
);
expectType<ToEmit<ServerToClientEventsNoAck, "helloFromServer">>(
nio.emit<"helloFromServer">
);
expectType<ToEmit<ServerToClientEventsNoAck, "ackFromServerSingleArg">>(
nio.emit<"ackFromServerSingleArg">
);
expectType<ToEmit<ServerToClientEventsNoAck, "ackFromServer">>(
nio.emit<"ackFromServer">
);
expectType<ToEmit<ServerToClientEventsNoAck, "onlyCallback">>(
nio.emit<"onlyCallback">
);
sio.on("connection", (s) => {
expectType<ToEmit<ServerToClientEvents, "helloFromServer">>(
s.emit<"helloFromServer">
Expand All @@ -425,6 +453,22 @@ describe("server", () => {
);
});
});
it("does not allow events with acks", () => {
const sio = new Server<ClientToServerEvents, ServerToClientEvents>();
const nio = sio.of("/test");
// @ts-expect-error - "ackFromServerSingleArg" has a callback and is thus excluded
sio.emit<"ackFromServerSingleArg">;
// @ts-expect-error - "ackFromServer" has a callback and is thus excluded
sio.emit<"ackFromServer">;
// @ts-expect-error - "onlyCallback" has a callback and is thus excluded
sio.emit<"onlyCallback">;
// @ts-expect-error - "ackFromServerSingleArg" has a callback and is thus excluded
nio.emit<"ackFromServerSingleArg">;
// @ts-expect-error - "ackFromServer" has a callback and is thus excluded
nio.emit<"ackFromServer">;
// @ts-expect-error - "onlyCallback" has a callback and is thus excluded
nio.emit<"onlyCallback">;
});
});
describe("emitWithAck", () => {
it("Infers correct types", () => {
Expand Down

0 comments on commit 6cf63e9

Please sign in to comment.