feat: Implement server-side player context for actions to prevent client tampering.
This commit is contained in:
@@ -9,7 +9,7 @@ interface DeckBuilderViewProps {
|
||||
initialPool: any[];
|
||||
}
|
||||
|
||||
export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ roomId, currentPlayerId, initialPool }) => {
|
||||
export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ initialPool }) => {
|
||||
const [timer, setTimer] = useState(45 * 60); // 45 minutes
|
||||
const [pool, setPool] = useState<any[]>(initialPool);
|
||||
const [deck, setDeck] = useState<any[]>([]);
|
||||
@@ -84,11 +84,11 @@ export const DeckBuilderView: React.FC<DeckBuilderViewProps> = ({ roomId, curren
|
||||
// Actually, user rules say "Host ... guided ... configuring packs ... multiplayer".
|
||||
|
||||
// I'll emit 'submit_deck' event (need to handle in server)
|
||||
socketService.socket.emit('player_ready', { roomId, playerId: currentPlayerId, deck: fullDeck });
|
||||
socketService.socket.emit('player_ready', { deck: fullDeck });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full bg-slate-900 text-white">
|
||||
<div className="flex-1 w-full flex h-full bg-slate-900 text-white">
|
||||
{/* Left: Pool */}
|
||||
<div className="w-1/2 p-4 flex flex-col border-r border-slate-700">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
|
||||
@@ -11,7 +11,7 @@ interface DraftViewProps {
|
||||
onExit?: () => void;
|
||||
}
|
||||
|
||||
export const DraftView: React.FC<DraftViewProps> = ({ draftState, roomId, currentPlayerId, onExit }) => {
|
||||
export const DraftView: React.FC<DraftViewProps> = ({ draftState, currentPlayerId, onExit }) => {
|
||||
const [timer, setTimer] = useState(60);
|
||||
const [confirmExitOpen, setConfirmExitOpen] = useState(false);
|
||||
|
||||
@@ -79,13 +79,14 @@ export const DraftView: React.FC<DraftViewProps> = ({ draftState, roomId, curren
|
||||
const pickedCards = draftState.players[currentPlayerId]?.pool || [];
|
||||
|
||||
const handlePick = (cardId: string) => {
|
||||
socketService.socket.emit('pick_card', { roomId, playerId: currentPlayerId, cardId });
|
||||
// roomId and playerId are now inferred by the server from socket session
|
||||
socketService.socket.emit('pick_card', { cardId });
|
||||
};
|
||||
|
||||
// ... inside DraftView return ...
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-slate-950 text-white overflow-hidden relative select-none" onContextMenu={(e) => e.preventDefault()}>
|
||||
<div className="flex-1 w-full flex flex-col h-full bg-slate-950 text-white overflow-hidden relative select-none" onContextMenu={(e) => e.preventDefault()}>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-slate-900 via-slate-950 to-black opacity-50 pointer-events-none"></div>
|
||||
|
||||
{/* Top Header: Timer & Pack Info */}
|
||||
|
||||
@@ -58,7 +58,6 @@ export const GameView: React.FC<GameViewProps> = ({ gameState, currentPlayerId }
|
||||
}
|
||||
|
||||
socketService.socket.emit('game_action', {
|
||||
roomId: gameState.roomId,
|
||||
action: {
|
||||
type: actionType,
|
||||
...safePayload
|
||||
@@ -92,7 +91,6 @@ export const GameView: React.FC<GameViewProps> = ({ gameState, currentPlayerId }
|
||||
}
|
||||
|
||||
socketService.socket.emit('game_action', {
|
||||
roomId: gameState.roomId,
|
||||
action
|
||||
});
|
||||
};
|
||||
@@ -103,7 +101,6 @@ export const GameView: React.FC<GameViewProps> = ({ gameState, currentPlayerId }
|
||||
|
||||
const toggleTap = (cardId: string) => {
|
||||
socketService.socket.emit('game_action', {
|
||||
roomId: gameState.roomId,
|
||||
action: {
|
||||
type: 'TAP_CARD',
|
||||
cardId
|
||||
@@ -272,7 +269,7 @@ export const GameView: React.FC<GameViewProps> = ({ gameState, currentPlayerId }
|
||||
<div className="w-40 p-2 flex flex-col gap-2 items-center justify-center border-r border-white/10">
|
||||
<div
|
||||
className="group relative w-16 h-24 bg-slate-800 rounded border border-slate-600 cursor-pointer shadow-lg transition-transform hover:-translate-y-1 hover:shadow-cyan-500/20"
|
||||
onClick={() => socketService.socket.emit('game_action', { roomId: gameState.roomId, action: { type: 'DRAW_CARD', playerId: currentPlayerId } })}
|
||||
onClick={() => socketService.socket.emit('game_action', { action: { type: 'DRAW_CARD' } })}
|
||||
onContextMenu={(e) => handleContextMenu(e, 'zone', undefined, 'library')}
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-slate-700 to-slate-800 rounded"></div>
|
||||
@@ -337,13 +334,13 @@ export const GameView: React.FC<GameViewProps> = ({ gameState, currentPlayerId }
|
||||
<div className="flex gap-1 mt-2 justify-center">
|
||||
<button
|
||||
className="w-8 h-8 rounded-full bg-slate-800 hover:bg-red-500/20 text-red-500 border border-slate-700 hover:border-red-500 transition-colors flex items-center justify-center font-bold"
|
||||
onClick={() => socketService.socket.emit('game_action', { roomId: gameState.roomId, action: { type: 'UPDATE_LIFE', playerId: currentPlayerId, amount: -1 } })}
|
||||
onClick={() => socketService.socket.emit('game_action', { action: { type: 'UPDATE_LIFE', amount: -1 } })}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<button
|
||||
className="w-8 h-8 rounded-full bg-slate-800 hover:bg-emerald-500/20 text-emerald-500 border border-slate-700 hover:border-emerald-500 transition-colors flex items-center justify-center font-bold"
|
||||
onClick={() => socketService.socket.emit('game_action', { roomId: gameState.roomId, action: { type: 'UPDATE_LIFE', playerId: currentPlayerId, amount: 1 } })}
|
||||
onClick={() => socketService.socket.emit('game_action', { action: { type: 'UPDATE_LIFE', amount: 1 } })}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user