Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
"[json]": {
"editor.defaultFormatter": "denoland.vscode-deno"
}
}
}
3 changes: 2 additions & 1 deletion examples/abort-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const receiver = new LogReceiver({
console.log("Log receiver running.. ");

receiver.on("event", (message) => console.log(message));
receiver.on("close", () => console.log("Closed the socket"));

controller.abort();

console.log("Closed the socket")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫣

12 changes: 7 additions & 5 deletions src/logReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { EventEmitter } from "node:events";
import { createSocket, type RemoteInfo, type Socket } from "node:dgram";
import { parsePacket } from "./parser.ts";
import type { EventData } from "./types.ts";
import type { EventData, TypedEventEmitter } from "./types.ts";

/**
* The socket options for the UDP socket
Expand Down Expand Up @@ -30,6 +30,11 @@ export interface LogReceiverOptions {
signal?: AbortSignal;
}

type MessageEvents = {
error: (error: Error) => void;
event: (message: EventData) => void;
};

/**
* An event emitter that will emit a message event when a valid UDP log is created on the server
*
Expand Down Expand Up @@ -94,7 +99,7 @@ export interface LogReceiverOptions {
*
* For security reasons, you should always use a log secret to prevent evaluation of potentially malicious messages. Do this by looking at the password field. In order to set up the log secret, you can use the `sv_logsecret` command
*/
export class LogReceiver extends EventEmitter implements Disposable {
export class LogReceiver extends (EventEmitter as new () => TypedEventEmitter<MessageEvents>) implements Disposable {
#socket: Socket;

/**
Expand All @@ -118,10 +123,7 @@ export class LogReceiver extends EventEmitter implements Disposable {

this.#socket.bind(port, address);

this.#socket.on("close", () => this.emit("close"));
this.#socket.on("connect", () => this.emit("connect"));
this.#socket.on("error", (error) => this.emit("error", error));
this.#socket.on("listening", () => this.emit("listening"));
this.#socket.on("message", (buffer, serverInfo) => this.#handleMessage(buffer, serverInfo));
}

Expand Down
44 changes: 44 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,47 @@ export interface EventData extends ParsedLogMessage {
*/
socket: RemoteInfo;
}

type EventMap = {
// deno-lint-ignore no-explicit-any
[key: string]: (...args: any[]) => void;
};

/**
* Copied from https://github.com/andywer/typed-emitter/blob/master/index.d.ts
* Type-safe event emitter
*
* Use it like this:
*
* ```ts
* type MyEvents = {
* error: (error: Error) => void;
* message: (from: string, content: string) => void;
* }
*
* const myEmitter = new EventEmitter() as TypedEmitter<MyEvents>;
*
* myEmitter.emit("error", "x") // <- Will catch this type error;
* ```
*/
export interface TypedEventEmitter<Events extends EventMap> {
addListener<E extends keyof Events>(event: E, listener: Events[E]): this;
on<E extends keyof Events>(event: E, listener: Events[E]): this;
once<E extends keyof Events>(event: E, listener: Events[E]): this;
prependListener<E extends keyof Events>(event: E, listener: Events[E]): this;
prependOnceListener<E extends keyof Events>(event: E, listener: Events[E]): this;

off<E extends keyof Events>(event: E, listener: Events[E]): this;
removeAllListeners<E extends keyof Events>(event?: E): this;
removeListener<E extends keyof Events>(event: E, listener: Events[E]): this;

emit<E extends keyof Events>(event: E, ...args: Parameters<Events[E]>): boolean;
// The sloppy `eventNames()` return type is to mitigate type incompatibilities - see #5
eventNames(): (keyof Events | string | symbol)[];
rawListeners<E extends keyof Events>(event: E): Events[E][];
listeners<E extends keyof Events>(event: E): Events[E][];
listenerCount<E extends keyof Events>(event: E): number;

getMaxListeners(): number;
setMaxListeners(maxListeners: number): this;
}
2 changes: 1 addition & 1 deletion tests/parser.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ Deno.bench("Can parse a password", { baseline: true, group: "password" }, () =>

assertEquals(result?.message, body.substring(2));
assertEquals(result?.password, password);
});
});
2 changes: 1 addition & 1 deletion tests/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ Deno.test("Can parse a password", () => {

assertEquals(result?.message, body.substring(2));
assertEquals(result?.password, password);
});
});