Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add strictly typed events #3822

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 41 additions & 14 deletions lib/broadcast-operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@ import type { BroadcastFlags, Room, SocketId } from "socket.io-adapter";
import { Handshake, RESERVED_EVENTS, Socket } from "./socket";
import { PacketType } from "socket.io-parser";
import type { Adapter } from "socket.io-adapter";
import type {
EventParams,
EventNames,
EventsMap,
DefaultEventsMap,
TypedEventBroadcaster,
} from "./typed-events";

export class BroadcastOperator {
export class BroadcastOperator<
UserEvents extends EventsMap,
UserEmitEvents extends EventsMap = UserEvents
> implements TypedEventBroadcaster<UserEmitEvents> {
constructor(
private readonly adapter: Adapter,
private readonly rooms: Set<Room> = new Set<Room>(),
Expand All @@ -18,7 +28,9 @@ export class BroadcastOperator {
* @return a new BroadcastOperator instance
* @public
*/
public to(room: Room | Room[]): BroadcastOperator {
public to(
room: Room | Room[]
): BroadcastOperator<UserEvents, UserEmitEvents> {
const rooms = new Set(this.rooms);
if (Array.isArray(room)) {
room.forEach((r) => rooms.add(r));
Expand All @@ -40,7 +52,9 @@ export class BroadcastOperator {
* @return a new BroadcastOperator instance
* @public
*/
public in(room: Room | Room[]): BroadcastOperator {
public in(
room: Room | Room[]
): BroadcastOperator<UserEvents, UserEmitEvents> {
return this.to(room);
}

Expand All @@ -51,7 +65,9 @@ export class BroadcastOperator {
* @return a new BroadcastOperator instance
* @public
*/
public except(room: Room | Room[]): BroadcastOperator {
public except(
room: Room | Room[]
): BroadcastOperator<UserEvents, UserEmitEvents> {
const exceptRooms = new Set(this.exceptRooms);
if (Array.isArray(room)) {
room.forEach((r) => exceptRooms.add(r));
Expand All @@ -73,7 +89,9 @@ export class BroadcastOperator {
* @return a new BroadcastOperator instance
* @public
*/
public compress(compress: boolean): BroadcastOperator {
public compress(
compress: boolean
): BroadcastOperator<UserEvents, UserEmitEvents> {
const flags = Object.assign({}, this.flags, { compress });
return new BroadcastOperator(
this.adapter,
Expand All @@ -91,7 +109,7 @@ export class BroadcastOperator {
* @return a new BroadcastOperator instance
* @public
*/
public get volatile(): BroadcastOperator {
public get volatile(): BroadcastOperator<UserEvents, UserEmitEvents> {
const flags = Object.assign({}, this.flags, { volatile: true });
return new BroadcastOperator(
this.adapter,
Expand All @@ -107,7 +125,7 @@ export class BroadcastOperator {
* @return a new BroadcastOperator instance
* @public
*/
public get local(): BroadcastOperator {
public get local(): BroadcastOperator<UserEvents, UserEmitEvents> {
const flags = Object.assign({}, this.flags, { local: true });
return new BroadcastOperator(
this.adapter,
Expand All @@ -123,18 +141,21 @@ export class BroadcastOperator {
* @return Always true
* @public
*/
public emit(ev: string | Symbol, ...args: any[]): true {
public emit<Ev extends EventNames<UserEmitEvents>>(
ev: Ev,
...args: EventParams<UserEmitEvents, Ev>
): true {
if (RESERVED_EVENTS.has(ev)) {
throw new Error(`"${ev}" is a reserved event name`);
}
// set up packet object
args.unshift(ev);
const data = [ev, ...args];
const packet = {
type: PacketType.EVENT,
data: args,
data: data,
};

if ("function" == typeof args[args.length - 1]) {
if ("function" == typeof data[data.length - 1]) {
throw new Error("Callbacks are not supported when broadcasting");
}

Expand Down Expand Up @@ -246,13 +267,16 @@ interface SocketDetails {
/**
* Expose of subset of the attributes and methods of the Socket class
*/
export class RemoteSocket {
export class RemoteSocket<
UserEvents extends EventsMap = DefaultEventsMap,
UserEmitEvents extends EventsMap = UserEvents
> implements TypedEventBroadcaster<UserEmitEvents> {
public readonly id: SocketId;
public readonly handshake: Handshake;
public readonly rooms: Set<Room>;
public readonly data: any;

private readonly operator: BroadcastOperator;
private readonly operator: BroadcastOperator<UserEvents, UserEmitEvents>;

constructor(adapter: Adapter, details: SocketDetails) {
this.id = details.id;
Expand All @@ -262,7 +286,10 @@ export class RemoteSocket {
this.operator = new BroadcastOperator(adapter, new Set([this.id]));
}

public emit(ev: string, ...args: any[]): boolean {
public emit<Ev extends EventNames<UserEmitEvents>>(
ev: Ev,
...args: EventParams<UserEmitEvents, Ev>
): true {
return this.operator.emit(ev, ...args);
}

Expand Down
15 changes: 11 additions & 4 deletions lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import debugModule = require("debug");
import url = require("url");
import type { IncomingMessage } from "http";
import type { Namespace, Server } from "./index";
import type { DefaultEventsMap, EventsMap } from "./typed-events";
import type { Socket } from "./socket";
import type { SocketId } from "socket.io-adapter";

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

export class Client {
export class Client<
UserEvents extends EventsMap = DefaultEventsMap,
UserEmitEvents extends EventsMap = UserEvents
> {
public readonly conn;

private readonly id: string;
private readonly server: Server;
private readonly server: Server<UserEvents, UserEmitEvents>;
private readonly encoder: Encoder;
private readonly decoder: Decoder;
private sockets: Map<SocketId, Socket> = new Map();
Expand All @@ -26,7 +30,10 @@ export class Client {
* @param conn
* @package
*/
constructor(server: Server, conn: Socket) {
constructor(
server: Server<UserEvents, UserEmitEvents>,
conn: Socket<UserEvents, UserEmitEvents>
) {
this.server = server;
this.conn = conn;
this.encoder = server.encoder;
Expand Down Expand Up @@ -87,7 +94,7 @@ export class Client {
this.server._checkNamespace(
name,
auth,
(dynamicNspName: Namespace | false) => {
(dynamicNspName: Namespace<UserEvents, UserEmitEvents> | false) => {
if (dynamicNspName) {
debug("dynamic namespace %s was created", dynamicNspName);
this.doConnect(name, auth);
Expand Down
59 changes: 42 additions & 17 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@ import { Adapter, Room, SocketId } from "socket.io-adapter";
import * as parser from "socket.io-parser";
import type { Encoder } from "socket.io-parser";
import debugModule from "debug";
import { Socket } from "./socket";
import { ServerReservedEventsMap, Socket } from "./socket";
import type { CookieSerializeOptions } from "cookie";
import type { CorsOptions } from "cors";
import type { BroadcastOperator, RemoteSocket } from "./broadcast-operator";
import {
EventsMap,
DefaultEventsMap,
EventParams,
StrictEventEmitter,
} from "./typed-events";

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

Expand Down Expand Up @@ -156,8 +162,15 @@ interface ServerOptions extends EngineAttachOptions {
connectTimeout: number;
}

export class Server extends EventEmitter {
public readonly sockets: Namespace;
export class Server<
UserEvents extends EventsMap = DefaultEventsMap,
UserEmitEvents extends EventsMap = UserEvents
> extends StrictEventEmitter<
UserEvents,
UserEmitEvents,
ServerReservedEventsMap<UserEvents, UserEmitEvents>
> {
public readonly sockets: Namespace<UserEvents, UserEmitEvents>;

/** @private */
readonly _parser: typeof parser;
Expand All @@ -167,8 +180,11 @@ export class Server extends EventEmitter {
/**
* @private
*/
_nsps: Map<string, Namespace> = new Map();
private parentNsps: Map<ParentNspNameMatchFn, ParentNamespace> = new Map();
_nsps: Map<string, Namespace<UserEvents, UserEmitEvents>> = new Map();
private parentNsps: Map<
ParentNspNameMatchFn,
ParentNamespace<UserEvents, UserEmitEvents>
> = new Map();
private _adapter?: typeof Adapter;
private _serveClient: boolean;
private opts: Partial<EngineOptions>;
Expand Down Expand Up @@ -248,7 +264,7 @@ export class Server extends EventEmitter {
_checkNamespace(
name: string,
auth: { [key: string]: any },
fn: (nsp: Namespace | false) => void
fn: (nsp: Namespace<UserEvents, UserEmitEvents> | false) => void
): void {
if (this.parentNsps.size === 0) return fn(false);

Expand Down Expand Up @@ -557,8 +573,8 @@ export class Server extends EventEmitter {
*/
public of(
name: string | RegExp | ParentNspNameMatchFn,
fn?: (socket: Socket) => void
): Namespace {
fn?: (socket: Socket<UserEvents, UserEmitEvents>) => void
): Namespace<UserEvents, UserEmitEvents> {
if (typeof name === "function" || name instanceof RegExp) {
const parentNsp = new ParentNamespace(this);
debug("initializing parent namespace %s", parentNsp.name);
Expand Down Expand Up @@ -616,7 +632,10 @@ export class Server extends EventEmitter {
* @public
*/
public use(
fn: (socket: Socket, next: (err?: ExtendedError) => void) => void
fn: (
socket: Socket<UserEvents, UserEmitEvents>,
next: (err?: ExtendedError) => void
) => void
): this {
this.sockets.use(fn);
return this;
Expand All @@ -629,7 +648,9 @@ export class Server extends EventEmitter {
* @return self
* @public
*/
public to(room: Room | Room[]): BroadcastOperator {
public to(
room: Room | Room[]
): BroadcastOperator<UserEvents, UserEmitEvents> {
return this.sockets.to(room);
}

Expand All @@ -640,7 +661,9 @@ export class Server extends EventEmitter {
* @return self
* @public
*/
public in(room: Room | Room[]): BroadcastOperator {
public in(
room: Room | Room[]
): BroadcastOperator<UserEvents, UserEmitEvents> {
return this.sockets.in(room);
}

Expand All @@ -651,7 +674,7 @@ export class Server extends EventEmitter {
* @return self
* @public
*/
public except(name: Room | Room[]): Server {
public except(name: Room | Room[]): Server<UserEvents, UserEmitEvents> {
this.sockets.except(name);
return this;
}
Expand All @@ -662,7 +685,7 @@ export class Server extends EventEmitter {
* @return self
* @public
*/
public send(...args: readonly any[]): this {
public send(...args: EventParams<UserEmitEvents, "message">): this {
this.sockets.emit("message", ...args);
return this;
}
Expand All @@ -673,7 +696,7 @@ export class Server extends EventEmitter {
* @return self
* @public
*/
public write(...args: readonly any[]): this {
public write(...args: EventParams<UserEmitEvents, "message">): this {
this.sockets.emit("message", ...args);
return this;
}
Expand All @@ -694,7 +717,9 @@ export class Server extends EventEmitter {
* @return self
* @public
*/
public compress(compress: boolean): BroadcastOperator {
public compress(
compress: boolean
): BroadcastOperator<UserEvents, UserEmitEvents> {
return this.sockets.compress(compress);
}

Expand All @@ -706,7 +731,7 @@ export class Server extends EventEmitter {
* @return self
* @public
*/
public get volatile(): BroadcastOperator {
public get volatile(): BroadcastOperator<UserEvents, UserEmitEvents> {
return this.sockets.volatile;
}

Expand All @@ -716,7 +741,7 @@ export class Server extends EventEmitter {
* @return self
* @public
*/
public get local(): BroadcastOperator {
public get local(): BroadcastOperator<UserEvents, UserEmitEvents> {
return this.sockets.local;
}

Expand Down