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

Connection disconnect reason #32

Merged
merged 7 commits into from
Aug 11, 2023
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
3 changes: 2 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"port": "25565"
"port": "25565",
"shutdownKickReason": "Server closed"
}
2 changes: 1 addition & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ process.on("SIGINT", () => {
});

server.on("packet.LoginPacket", (packet, conn) => {
new LoginSuccessPacket(packet.data.uuid, packet.data.username).send(conn);
new LoginSuccessPacket(packet.data.uuid, packet.data.username).send(conn).then();
});
5 changes: 5 additions & 0 deletions src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import Logger from "./Logger.js";
export default class Config {
public port: number = 25565;

/**
* Kick reason for when the server is shutting down
*/
public shutdownKickReason: string = "Server closed";

/**
* Get a Config instance from a json file
* @param file The file to read from
Expand Down
60 changes: 56 additions & 4 deletions src/Connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Packet from "./Packet.js";
/**
* A TCP socket connection to the server.
*/
export default class Connection {
class Connection {
/**
* A unique identifier for this connection.
*/
Expand All @@ -19,6 +19,22 @@ export default class Connection {
* The server to which this connection belongs.
*/
public readonly server: Server;
/**
* The state of the connection.
*/
#state: Connection.State = Connection.State.NONE;

/**
* The state of the connection.
*/
public get state(): Connection.State {
return this.#state;
}

/** @internal */
public _setState(state: Connection.State): void {
this.#state = state;
}

/**
* Packet fragment this connection is currently sending to the server.
Expand All @@ -37,9 +53,9 @@ export default class Connection {
if (this.currentPacketFragment.push(data)) {
const p = this.currentPacketFragment.getTypedClient();
if (p) {
p.execute(this, this.server);
this.server.emit("packet", p, this);
this.server.emit(`packet.${p.constructor.name}` as any, p, this);
p.execute(this, this.server);
}
else this.server.emit("unknownPacket", this.currentPacketFragment, this);
this.currentPacketFragment = new Packet();
Expand All @@ -48,9 +64,10 @@ export default class Connection {

/**
* Disconnect this connection.
* @param [reason] The reason for the disconnect.
*/
public disconnect(): Promise<boolean> {
return this.server.connections.disconnect(this.id);
public disconnect(reason?: string): Promise<boolean> {
return this.server.connections.disconnect(this.id, reason);
}

/**
Expand All @@ -60,3 +77,38 @@ export default class Connection {
return !this.socket.destroyed && this.server.connections.get(this.id) !== null;
}
}

namespace Connection {
/**
* Connection state
*/
export enum State {
/**
* None / unknown
*/
NONE,

/**
* Status state
*
* Sender is checking server status
*/
STATUS,

/**
* Login state
*
* Player is connecting to the server
*/
LOGIN,

/**
* Play state
*
* Player is online and communicating game data
*/
PLAY
}
}

export default Connection;
48 changes: 32 additions & 16 deletions src/ConnectionPool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Connection from "./Connection";
import Connection from "./Connection.js";
import DisconnectLoginPacket from "./packet/server/DisconnectLoginPacket.js";
import DisconnectPlayPacket from "./packet/server/DisconnectPlayPacket.js";

export default class ConnectionPool {
private readonly connections: Connection[] = [];
Expand All @@ -22,28 +24,42 @@ export default class ConnectionPool {

/**
* Disconnect all connections
* @param [reason] The reason for the disconnect
* @returns Whether all connections disconnected successfully
*/
public async disconnect(): Promise<boolean>;
public async disconnectAll (reason?: string): Promise<boolean> {
const promises: Promise<boolean>[] = [];
for (const connection of this.connections)
promises.push(this.disconnect(connection.id, reason));
return (await Promise.all(promises)).every(result => result);
}

/**
* Disconnect a connection
* @param id The ID of the connection to disconnect
* @param [reason] The reason for the disconnect
* @returns Whether the connection was found and disconnected
*/
public async disconnect(id: string): Promise<boolean>;
public async disconnect(id?: string): Promise<boolean> {
const promises: Promise<boolean>[] = [];
if (id) {
const connection = this.get(id);
if (!connection) return false;
const index = this.connections.indexOf(connection);
if (index === -1) return false;
this.connections.splice(index, 1);
connection.server.emit("disconnect", connection);
promises.push(new Promise(resolve => connection.socket.end(() => resolve(true))));
public async disconnect(id: string, reason?: string): Promise<boolean> {
const connection = this.get(id);
if (!connection) return false;
const index = this.connections.indexOf(connection);
if (index === -1) return false;
if (reason) switch (connection.state) {
case Connection.State.LOGIN: {
await new DisconnectLoginPacket({text: reason}).send(connection);
break;
}
case Connection.State.PLAY: {
await new DisconnectPlayPacket({text: reason}).send(connection);
break;
}
default: {
connection.server.logger.warn("Cannot set disconnect reason for state " + Connection.State[connection.state] + " on connection " + connection.id);
}
}
else for (const connection of this.connections)
promises.push(this.disconnect(connection.id));
return (await Promise.all(promises)).every(result => result);
this.connections.splice(index, 1);
connection.server.emit("disconnect", connection);
return new Promise(resolve => connection.socket.end(() => resolve(true)));
}
}
18 changes: 17 additions & 1 deletion src/Packet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ParsedPacket from "./ParsedPacket.js";
import {TypedClientPacket, TypedClientPacketStatic} from "./TypedPacket";
import {TypedClientPacket, TypedClientPacketStatic} from "./types/TypedPacket";
import HandshakePacket from "./packet/client/HandshakePacket.js";
import LoginPacket from "./packet/client/LoginPacket.js";

Expand Down Expand Up @@ -178,6 +178,22 @@ export default class Packet {
return buffer;
}

/**
* Parse chat
* @param buffer
*/
public static parseChat(buffer: Buffer): ChatComponent {
return JSON.parse(Packet.parseString(buffer)) as ChatComponent;
}

/**
* Write chat
* @param value
*/
public static writeChat(value: ChatComponent): Buffer {
return Packet.writeString(JSON.stringify(value));
}

/**
* Get typed client packet
*/
Expand Down
8 changes: 4 additions & 4 deletions src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import path from "node:path";
import Packet from "./Packet.js";
import Config from "./Config.js";
import Logger from "./Logger.js";
import {TypedClientPacket} from "./TypedPacket";
import TypedEventEmitter from "./TypedEventEmitter";
import {TypedClientPacket} from "./types/TypedPacket";
import TypedEventEmitter from "./types/TypedEventEmitter";
import ConnectionPool from "./ConnectionPool.js";
import Connection from "./Connection.js";
import HandshakePacket from "./packet/client/HandshakePacket";
Expand Down Expand Up @@ -91,8 +91,8 @@ export default class Server extends (EventEmitter as new () => TypedEventEmitter
else resolve(void 0);
});
}),
this.connections.disconnect(),
]).then(() => void 0);
this.connections.disconnectAll(this.config.shutdownKickReason),
]);
this.emit("closed");
}

Expand Down
9 changes: 7 additions & 2 deletions src/ServerPacket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ export default abstract class ServerPacket extends Packet {
* Send packet to a connection
* @param connection
*/
public send(connection: Connection): void {
connection.socket.write(this.dataBuffer);
public send(connection: Connection): Promise<void> {
return new Promise((resolve, reject) => {
connection.socket.write(this.dataBuffer, (err) => {
if (err) reject(err);
else resolve();
});
});
}
}
8 changes: 5 additions & 3 deletions src/packet/client/HandshakePacket.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {TypedClientPacket, TypedClientPacketStatic} from "../../TypedPacket";
import {TypedClientPacket, TypedClientPacketStatic} from "../../types/TypedPacket";
import StaticImplements from "../../decorator/StaticImplements.js";
import Server from "../../Server";
import ParsedPacket from "../../ParsedPacket";
import Connection from "../../Connection";
import Connection from "../../Connection.js";

@StaticImplements<TypedClientPacketStatic>()
export default class HandshakePacket {
Expand All @@ -25,7 +25,9 @@ export default class HandshakePacket {
} as const;
}

execute(_conn: Connection, _server: Server): void {}
execute(conn: Connection, _server: Server): void {
conn._setState(Connection.State.LOGIN);
}

public static readonly id = 0x00;

Expand Down
8 changes: 5 additions & 3 deletions src/packet/client/LoginPacket.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {TypedClientPacket, TypedClientPacketStatic} from "../../TypedPacket";
import {TypedClientPacket, TypedClientPacketStatic} from "../../types/TypedPacket";
import StaticImplements from "../../decorator/StaticImplements.js";
import ParsedPacket from "../../ParsedPacket.js";
import Server from "../../Server";
import Connection from "../../Connection";
import Connection from "../../Connection.js";

@StaticImplements<TypedClientPacketStatic>()
export default class LoginPacket {
Expand All @@ -24,7 +24,9 @@ export default class LoginPacket {
}
}

execute(_conn: Connection, _server: Server): void {}
execute(conn: Connection, _server: Server): void {
conn._setState(Connection.State.LOGIN);
}

public static readonly id = 0x00;

Expand Down
12 changes: 12 additions & 0 deletions src/packet/server/DisconnectLoginPacket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ServerPacket from "../../ServerPacket.js";

export default class DisconnectLoginPacket extends ServerPacket {
public static readonly id = 0x00;

public constructor(reason: ChatComponent) {
super(Buffer.concat([
ServerPacket.writeVarInt(DisconnectLoginPacket.id),
ServerPacket.writeChat(reason)
]));
}
}
12 changes: 12 additions & 0 deletions src/packet/server/DisconnectPlayPacket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ServerPacket from "../../ServerPacket.js";

export default class DisconnectPlayPacket extends ServerPacket {
public static readonly id = 0x1a;

public constructor(reason: ChatComponent) {
super(Buffer.concat([
ServerPacket.writeVarInt(DisconnectPlayPacket.id),
ServerPacket.writeChat(reason)
]));
}
}
6 changes: 6 additions & 0 deletions src/packet/server/LoginSuccessPacket.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ServerPacket from "../../ServerPacket.js";
import Connection from "../../Connection.js";

/**
* A Minecraft protocol client-bound LoginSuccess packet.
Expand All @@ -14,4 +15,9 @@ export default class LoginSuccessPacket extends ServerPacket {
ServerPacket.writeVarInt(0)
]));
}

public override send(connection: Connection) {
connection._setState(Connection.State.PLAY);
return super.send(connection);
}
}
25 changes: 25 additions & 0 deletions src/types/ChatComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
type ChatComponent = {
bold?: boolean;
italic?: boolean;
underlined?: boolean;
strikethrough?: boolean;
obfuscated?: boolean;
font?: string;
color?: string;
insertion?: string;
clickEvent?: {
action: "open_url" | "open_file" | "run_command" | "suggest_command" | "change_page" | "copy_to_clipboard";
value: string;
}
hoverEvent?: {
action: "show_text" | "show_item" | "show_entity";
}
text?: string;
extra?: [ChatComponent, ...ChatComponent[]];
} | {translate: string; with?: ChatComponent[]} | {keybind: string} | {
score: {
name: string;
objective: string;
value?: string;
}
};
File renamed without changes.
6 changes: 3 additions & 3 deletions src/TypedPacket.ts → src/types/TypedPacket.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Server from "./Server";
import ParsedPacket from "./ParsedPacket";
import Connection from "./Connection";
import Server from "../Server";
import ParsedPacket from "../ParsedPacket";
import Connection from "../Connection";

export interface TypedClientPacket {
readonly packet: ParsedPacket;
Expand Down
Loading