only one login
This commit is contained in:
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user