fix: 修复客户端中继协议 + 全平台房间详情/分享
关键修复: - RelayClient: 二进制头部改为FUNMC_JOIN:roomId|playerName|password协议 - RelayClient: 等待服务端OK:CONNECTED/ERROR:*握手响应 - rooms:join: 先连接中继再启动本地代理, 传入playerName和password - 连接失败自动cleanup Web管理面板: - 房间详情弹窗: 点击房间卡片打开 - 玩家列表 + 踢出功能 (UserX图标) - 复制房间号 / 删除房间按钮 Mobile: - 房间详情底部弹窗 (Modal slide) - 在线玩家列表 - 分享房间号 (Share API) - 复制房间号 - apiClient.getRoomDetail 方法
This commit is contained in:
@@ -147,18 +147,13 @@ ipcMain.handle('rooms:delete', async (_event, roomId: string) => {
|
||||
});
|
||||
|
||||
// Join room - start local proxy and relay connection
|
||||
ipcMain.handle('rooms:join', async (_event, opts: { serverHost: string; serverPort: number; roomId: string; localPort: number }) => {
|
||||
ipcMain.handle('rooms:join', async (_event, opts: { serverHost: string; serverPort: number; roomId: string; localPort: number; password?: string }) => {
|
||||
try {
|
||||
cleanup();
|
||||
|
||||
relayClient = new RelayClient(opts.serverHost, opts.serverPort, opts.roomId);
|
||||
localProxy = new LocalProxy(opts.localPort, relayClient);
|
||||
const playerName = (store.get('playerName') as string) || 'Player';
|
||||
relayClient = new RelayClient(opts.serverHost, opts.serverPort, opts.roomId, playerName, opts.password);
|
||||
|
||||
await localProxy.start();
|
||||
|
||||
relayClient.on('connected', () => {
|
||||
mainWindow?.webContents.send('relay:status', { status: 'connected' });
|
||||
});
|
||||
relayClient.on('disconnected', () => {
|
||||
mainWindow?.webContents.send('relay:status', { status: 'disconnected' });
|
||||
});
|
||||
@@ -166,8 +161,17 @@ ipcMain.handle('rooms:join', async (_event, opts: { serverHost: string; serverPo
|
||||
mainWindow?.webContents.send('relay:status', { status: 'error', error: err });
|
||||
});
|
||||
|
||||
// Connect to relay server first (handshake)
|
||||
await relayClient.connect();
|
||||
|
||||
// Then start local proxy for Minecraft to connect to
|
||||
localProxy = new LocalProxy(opts.localPort, relayClient);
|
||||
await localProxy.start();
|
||||
|
||||
mainWindow?.webContents.send('relay:status', { status: 'connected' });
|
||||
return { success: true, localPort: opts.localPort };
|
||||
} catch (err: any) {
|
||||
cleanup();
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,7 +9,9 @@ export class RelayClient extends EventEmitter {
|
||||
constructor(
|
||||
private host: string,
|
||||
private port: number,
|
||||
private roomId: string
|
||||
private roomId: string,
|
||||
private playerName: string = 'Player',
|
||||
private password?: string
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -17,21 +19,35 @@ export class RelayClient extends EventEmitter {
|
||||
connect(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket = new net.Socket();
|
||||
let handshakeDone = false;
|
||||
|
||||
this.socket.connect(this.port, this.host, () => {
|
||||
this.connected = true;
|
||||
// Send room ID as initial handshake
|
||||
const header = Buffer.alloc(2 + this.roomId.length);
|
||||
header.writeUInt16BE(this.roomId.length, 0);
|
||||
header.write(this.roomId, 2);
|
||||
this.socket!.write(header);
|
||||
this.emit('connected');
|
||||
resolve();
|
||||
// Send FUNMC_JOIN handshake matching server relay protocol
|
||||
const parts = [this.roomId, this.playerName];
|
||||
if (this.password) parts.push(this.password);
|
||||
this.socket!.write(`FUNMC_JOIN:${parts.join('|')}\n`);
|
||||
});
|
||||
|
||||
// Wait for server response before marking as connected
|
||||
this.socket.once('data', (data) => {
|
||||
const response = data.toString('utf8').trim();
|
||||
handshakeDone = true;
|
||||
if (response.startsWith('OK:')) {
|
||||
this.connected = true;
|
||||
this.socket!.setTimeout(0); // clear timeout after handshake
|
||||
this.emit('connected');
|
||||
resolve();
|
||||
} else {
|
||||
// e.g. ERROR:ROOM_NOT_FOUND, ERROR:WRONG_PASSWORD, ERROR:ROOM_FULL, ERROR:HOST_OFFLINE
|
||||
const errorMsg = response.replace('ERROR:', '');
|
||||
this.socket!.destroy();
|
||||
reject(new Error(errorMsg));
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('error', (err) => {
|
||||
this.emit('error', err.message);
|
||||
if (!this.connected) reject(err);
|
||||
if (!handshakeDone) reject(err);
|
||||
});
|
||||
|
||||
this.socket.on('close', () => {
|
||||
@@ -40,7 +56,9 @@ export class RelayClient extends EventEmitter {
|
||||
});
|
||||
|
||||
this.socket.setTimeout(10000, () => {
|
||||
this.socket?.destroy(new Error('Connection timeout'));
|
||||
if (!handshakeDone) {
|
||||
this.socket?.destroy(new Error('Connection timeout'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user