only one login

This commit is contained in:
sebseb7
2026-05-21 09:17:14 +02:00
parent bf2bed5599
commit 2a9b996453
2 changed files with 77 additions and 24 deletions

View File

@@ -6,14 +6,28 @@ const { disableInboundChatValidation } = require('../utils/chatRelay');
const log = createLogger('ProxyServer');
class ProxyServer {
constructor(config, onClientConnect, worldState) {
/**
* @param {object} config
* @param {(client: object) => void} onClientConnect
* @param {import('../state/WorldStateCache').WorldStateCache} worldState
* @param {() => { ok: boolean, reason?: string }} [canAcceptClient]
*/
constructor(config, onClientConnect, worldState, canAcceptClient) {
this.config = config;
this.onClientConnect = onClientConnect;
this.worldState = worldState;
this.canAcceptClient = canAcceptClient;
this.server = null;
this.activeClient = null;
}
/** Clear the single-client slot if it belongs to this connection. */
releaseClient(client) {
if (this.activeClient === client) {
this.activeClient = null;
}
}
start() {
this.server = mc.createServer({
host: this.config.proxy.host || '0.0.0.0',
@@ -31,8 +45,26 @@ class ProxyServer {
},
});
// Replay upstream server's raw config packets before minecraft-protocol's parsed registry.
this.server.on('login', (client) => {
wrapClientEnd(client);
const slot = this.canAcceptClient?.() ?? {
ok: !this.activeClient,
reason: 'Only one client can connect at a time.',
};
if (!slot.ok || this.activeClient) {
const reason = slot.reason || 'Only one client can connect at a time.';
log.warn(`Rejecting login for ${client.username || 'client'}: ${reason}`);
client.end(reason);
return;
}
this.activeClient = client;
client.on('end', () => {
log.info(`Client disconnected: ${client.username}`);
this.releaseClient(client);
});
client.prependOnceListener('login_acknowledged', () => {
const packets = this.worldState.getRawConfigPacketsForReplay();
if (packets.length === 0) return;
@@ -49,24 +81,15 @@ class ProxyServer {
});
this.server.on('playerJoin', (client) => {
wrapClientEnd(client);
disableInboundChatValidation(client);
log.info(`Client ready: ${client.username}`);
if (this.activeClient) {
client.end('Another client is already connected.');
if (this.activeClient !== client) {
log.warn(`Rejecting playerJoin for ${client.username} — not the active client`);
client.end('Only one client can connect at a time.');
return;
}
this.activeClient = client;
client.on('end', () => {
log.info(`Client disconnected: ${client.username}`);
if (this.activeClient === client) {
this.activeClient = null;
}
});
log.info(`Client ready: ${client.username}`);
this.onClientConnect(client);
});

View File

@@ -37,7 +37,12 @@ class SessionManager {
// Core components
this.worldState = new WorldStateCache(config);
this.serverConn = new ServerConnection(config, this.worldState);
this.proxyServer = new ProxyServer(config, (client) => this._onClientConnect(client), this.worldState);
this.proxyServer = new ProxyServer(
config,
(client) => this._onClientConnect(client),
this.worldState,
() => this._clientSlotStatus(),
);
this.replayer = new StateReplayer(this.worldState, this.serverConn);
// Current client bridge (if in CLIENT_MODE)
@@ -207,19 +212,42 @@ class SessionManager {
}
}
/**
* Whether a new Java client may take the single proxy slot.
* @returns {{ ok: boolean, reason?: string }}
*/
_clientSlotStatus() {
if (this.currentClient || this.proxyServer.activeClient) {
return { ok: false, reason: 'Only one client can connect at a time.' };
}
if (this.state === State.INIT) {
return {
ok: false,
reason: 'Proxy is still connecting to the server. Try again in a moment.',
};
}
if (this.state !== State.BOT_MODE) {
return { ok: false, reason: 'Another client session is active.' };
}
return { ok: true };
}
_rejectClient(client, reason) {
log.warn(`Rejecting ${client.username}: ${reason}`);
client.end(reason);
this.proxyServer.releaseClient(client);
}
/**
* Handle a new Java client connection from the proxy server.
*/
async _onClientConnect(client) {
if (this.state === State.INIT) {
log.warn('Client connected but bot is not ready yet — rejecting');
client.end('Proxy is still connecting to the server. Try again in a moment.');
if (this.proxyServer.activeClient !== client) {
this._rejectClient(client, 'Only one client can connect at a time.');
return;
}
if (this.state === State.HANDOFF || this.state === State.CLIENT_MODE) {
log.warn('Client connected but another client is active — rejecting');
client.end('Another client session is active.');
if (this.state !== State.BOT_MODE || this.currentClient) {
this._rejectClient(client, 'Another client session is active.');
return;
}
@@ -279,8 +307,10 @@ class SessionManager {
this.clientBridge.stop();
this.clientBridge = null;
}
if (this.currentClient) {
this.proxyServer.releaseClient(this.currentClient);
}
this.currentClient = null;
this.proxyServer.activeClient = null;
}
/**