Skip to content

Commit

Permalink
feat: add signal support, forward socket events, add additional docum…
Browse files Browse the repository at this point in the history
…entation
  • Loading branch information
c43721 committed May 23, 2024
1 parent 06e2ceb commit c295ac6
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 63 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Publish Package
on:
push:
branches:
- main

jobs:
publish:
runs-on: ubuntu-latest

permissions:
contents: read
id-token: write

steps:
- uses: actions/checkout@v4

- name: Publish package
run: npx jsr publish
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 rcon.tf
Copyright (c) 2024 rcon.tf

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
34 changes: 14 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
# srcds-logs

Wrapper around srcds udp log streams for node
Wrapper around [Source Dedicated Server](https://developer.valvesoftware.com/wiki/Source_Dedicated_Server) UDP log streams for node.

# Usage
## Set Up

You can define a receiver and listen to messages published to the UDP stream
Your server must have logs enabled. To do so, run `log on` in your server console or through RCON. In order to receieve messages, you must also add a log address using `logaddress_add` command to add the URL or IP of your server's receiever. To delete a destination, use `logaddress_del`.

```ts
import { LogReceiver } from "srcds";
You can always list existing addresses using `logaddress_list`.

const receiver = new LogReceiver({
address: "0.0.0.0",
port: 9871,
});
# Installation

console.log("Log receiver running.. ");
See https://jsr.io/@c43721/srcds-log-receiver for more details.

receiver.on("event", (message) => console.log(message));
```
# Usage

In order to check for passwords (in cases where it's important to not have people publish unwanted packets to your server) you can check the password property on the object.
You can define a receiver and listen to messages published to the UDP stream.

```ts
import { LogReceiver } from "srcds";
import { LogReceiver } from "@c43721/srcds-log-receiver";

const receiver = new LogReceiver({
address: "0.0.0.0",
Expand All @@ -31,12 +26,11 @@ const receiver = new LogReceiver({

console.log("Log receiver running.. ");

receiver.on("event", (message) => {
if (message.password === "mysupersecretpassword") {
// do something awesome
}
});
receiver.on("event", (message) => console.log(message));
```

For more examples, see documentation or the examples folder.

# Contributing
If there's a feature or bug, please raise a github issue first alongside your PR (if you're kind enough to make a PR)

If there's a feature or bug, please raise a github issue first alongside your PR (if you're kind enough to make a PR.)
17 changes: 17 additions & 0 deletions examples/abort-controller.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { LogReceiver } from "@c43721/srcds-log-receiver";

const controller = new AbortController();
const { signal } = controller;

const receiver = new LogReceiver({
address: "0.0.0.0",
port: 9871,
signal,
});

console.log("Log receiver running.. ");

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

controller.abort();
2 changes: 1 addition & 1 deletion examples/write-to-console.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LogReceiver } from "srcds";
import { LogReceiver } from "@c43721/srcds-log-receiver";

const receiver = new LogReceiver({
address: "0.0.0.0",
Expand Down
5 changes: 4 additions & 1 deletion jsr.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"name": "@c43721/srcds-log-receiver",
"version": "1.0.1",
"exports": "./src/index.ts"
"exports": "./src/index.ts",
"publish": {
"exclude": ["./examples"]
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { LogReceiver, type LogReceiverOptions } from "./logReceiver";
export { EventData } from "./types";
88 changes: 73 additions & 15 deletions src/logReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,75 @@ import { Buffer } from "node:buffer";
import { EventEmitter } from "node:events";
import { type Socket, createSocket, type RemoteInfo } from "node:dgram";
import { parsePacket } from "./parser";
import { EventData } from "./types";

/**
* The socket options for the UDP socket
*/
export interface LogReceiverOptions {
/**
* The IP address to bind to
* The address to listen on
*
* @default "0.0.0.0"
*/
address?: string;

/**
* The port to use
*
* @default 9871
*/
port?: number;

/**
* The abort signal to use
*
* When calling {@link AbortSignal.abort}, it will automatically close the underlying socket
*/
signal?: AbortSignal;
}

/**
* An event emitter that will emit a message event when a valid UDP log is created on the server
*
*
* When a log is sent, there will be a `message` event published that you can extract the data from. This is a valid payload. It is important to check the IP or password of the content to ensure it came from a trusted source
*
* You can listen on socket events such as `error` and `close` by listening to those events. All default events of the {@link Socket} will be forwarded. If you need to close the socket at any time, provide an {@link AbortSignal}. Aborting will automatically call {@link Socket.close} for you on the socket and emits a `close` event
*
* @example Simple receiver that logs to console every message
* ```ts
import { LogReceiver } from "srcds";
const receiver = new LogReceiver({
address: "0.0.0.0",
port: 9871,
});
receiver.on("event", (message) => console.log(message));
```
* import { LogReceiver} from "@c43721/srcds-log-receiver";
*
* const receiver = new LogReceiver({
* address: "0.0.0.0",
* port: 9871,
* });
*
* receiver.on("event", (message) => console.log(message));
* ```
*
* On every log generated by the srcds server, it will log that message out to console
*
* @example Passing in an abort controller to close the socket
* ```ts
* import { LogReceiver} from "@c43721/srcds-log-receiver";
*
* const controller = new AbortController();
* const { signal } = controller;
*
* const receiver = new LogReceiver({
* address: "0.0.0.0",
* port: 9871,
* });
*
* receiver.on("event", (message) => console.log(message));
* receiver.on("close", () => console.log("Closed the socket"));
*
* controller.abort();
* ```
*
* While not required, there may be times where you have short-lived receivers. Since each instance creates a socket, closing those sockets becomes a problem. Abort controllers help with that by providing a way to explicitly close the resources for you
*
*/
export class LogReceiver extends EventEmitter {
#socket: Socket;
Expand All @@ -40,20 +80,38 @@ export class LogReceiver extends EventEmitter {
* @param options The log reciever options to use
*/
constructor(
{ address, port }: LogReceiverOptions = { address: "0.0.0.0", port: 9871 }
options: LogReceiverOptions = {
address: "0.0.0.0",
port: 9871,
}
) {
const { address, port, signal } = options;

super();
this.#socket = createSocket("udp4", this.#handleMessage.bind(this));

this.#socket = createSocket({
type: "udp4",
signal,
});

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));
}

#handleMessage(buffer: Buffer, serverInfo: RemoteInfo) {
const response = parsePacket(buffer, serverInfo);
const response = parsePacket(buffer);

if (response == null) {
return;
}

this.emit("event", response);
const eventData = { ...response, socket: serverInfo } as EventData;

this.emit("event", eventData);
}
}
12 changes: 2 additions & 10 deletions src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { Buffer } from "node:buffer";
import type { RemoteInfo } from "node:dgram";
import { logMessageEndChar, packetHeader, passwordFlag } from "./constants";
import { ParsedLogMessage } from "./types";

/**
* Parses and validates the incoming buffer
* @private
* @param message The message from the socket
* @param serverInfo The socket information of the connecting resource
* @returns An object that contains the log message, the password used, and the socket information or null if no message was parsed
*/
export function parsePacket(
message: Buffer,
serverInfo: RemoteInfo
): ParsedLogMessage | null {
export function parsePacket(message: Buffer): ParsedLogMessage | null {
if (message.length < 16) {
return null;
}
Expand Down Expand Up @@ -47,9 +43,5 @@ export function parsePacket(
return {
password,
message: fullLogMessage,
socket: {
ip: serverInfo.address,
port: serverInfo.port,
},
};
}
24 changes: 9 additions & 15 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import { type RemoteInfo } from "node:dgram";

/**
* Object holding the parsed information from the UDP log
* Represents the parsed information from the message data
*/
export interface ParsedLogMessage {
/**
* The password sent by the server
* The password sent by the server, if present
*/
password: string | null;

/**
* The log the server sent
* The message contained in the data
*/
message: string;
}

export interface EventData extends ParsedLogMessage {
/**
* The socket information of the server
* The remote address information that sent the packet
*/
socket: {
/**
* The source IP of the message
*/
ip: string;

/**
* The port of the server
*/
port: number;
};
socket: RemoteInfo;
}

0 comments on commit c295ac6

Please sign in to comment.