only one login
This commit is contained in:
@@ -6,14 +6,28 @@ const { disableInboundChatValidation } = require('../utils/chatRelay');
|
|||||||
const log = createLogger('ProxyServer');
|
const log = createLogger('ProxyServer');
|
||||||
|
|
||||||
class 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.config = config;
|
||||||
this.onClientConnect = onClientConnect;
|
this.onClientConnect = onClientConnect;
|
||||||
this.worldState = worldState;
|
this.worldState = worldState;
|
||||||
|
this.canAcceptClient = canAcceptClient;
|
||||||
this.server = null;
|
this.server = null;
|
||||||
this.activeClient = 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() {
|
start() {
|
||||||
this.server = mc.createServer({
|
this.server = mc.createServer({
|
||||||
host: this.config.proxy.host || '0.0.0.0',
|
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) => {
|
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', () => {
|
client.prependOnceListener('login_acknowledged', () => {
|
||||||
const packets = this.worldState.getRawConfigPacketsForReplay();
|
const packets = this.worldState.getRawConfigPacketsForReplay();
|
||||||
if (packets.length === 0) return;
|
if (packets.length === 0) return;
|
||||||
@@ -49,24 +81,15 @@ class ProxyServer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.server.on('playerJoin', (client) => {
|
this.server.on('playerJoin', (client) => {
|
||||||
wrapClientEnd(client);
|
|
||||||
disableInboundChatValidation(client);
|
disableInboundChatValidation(client);
|
||||||
log.info(`Client ready: ${client.username}`);
|
|
||||||
|
|
||||||
if (this.activeClient) {
|
if (this.activeClient !== client) {
|
||||||
client.end('Another client is already connected.');
|
log.warn(`Rejecting playerJoin for ${client.username} — not the active client`);
|
||||||
|
client.end('Only one client can connect at a time.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeClient = client;
|
log.info(`Client ready: ${client.username}`);
|
||||||
|
|
||||||
client.on('end', () => {
|
|
||||||
log.info(`Client disconnected: ${client.username}`);
|
|
||||||
if (this.activeClient === client) {
|
|
||||||
this.activeClient = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.onClientConnect(client);
|
this.onClientConnect(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,12 @@ class SessionManager {
|
|||||||
// Core components
|
// Core components
|
||||||
this.worldState = new WorldStateCache(config);
|
this.worldState = new WorldStateCache(config);
|
||||||
this.serverConn = new ServerConnection(config, this.worldState);
|
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);
|
this.replayer = new StateReplayer(this.worldState, this.serverConn);
|
||||||
|
|
||||||
// Current client bridge (if in CLIENT_MODE)
|
// 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.
|
* Handle a new Java client connection from the proxy server.
|
||||||
*/
|
*/
|
||||||
async _onClientConnect(client) {
|
async _onClientConnect(client) {
|
||||||
if (this.state === State.INIT) {
|
if (this.proxyServer.activeClient !== client) {
|
||||||
log.warn('Client connected but bot is not ready yet — rejecting');
|
this._rejectClient(client, 'Only one client can connect at a time.');
|
||||||
client.end('Proxy is still connecting to the server. Try again in a moment.');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.state !== State.BOT_MODE || this.currentClient) {
|
||||||
if (this.state === State.HANDOFF || this.state === State.CLIENT_MODE) {
|
this._rejectClient(client, 'Another client session is active.');
|
||||||
log.warn('Client connected but another client is active — rejecting');
|
|
||||||
client.end('Another client session is active.');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,8 +307,10 @@ class SessionManager {
|
|||||||
this.clientBridge.stop();
|
this.clientBridge.stop();
|
||||||
this.clientBridge = null;
|
this.clientBridge = null;
|
||||||
}
|
}
|
||||||
|
if (this.currentClient) {
|
||||||
|
this.proxyServer.releaseClient(this.currentClient);
|
||||||
|
}
|
||||||
this.currentClient = null;
|
this.currentClient = null;
|
||||||
this.proxyServer.activeClient = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user