diff --git a/client/src/App.tsx b/client/src/App.tsx index 3e3723b..74ea2dc 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { Chessboard } from 'react-chessboard'; import EvaluationBar from './components/EvaluationBar'; import OpponentSelector from './components/OpponentSelector'; @@ -6,19 +6,108 @@ import LoginForm from './components/LoginForm'; import RegisterForm from './components/RegisterForm'; import { useChessGame } from './hooks/useChessGame'; import { useOpponent } from './hooks/useOpponent'; -// Remove the Human option from the OpponentSelector component import { useAuth } from './hooks/useAuth'; import { useWebSocket } from './hooks/useWebSocket'; +import { OnlineUser, WebSocketMessage } from '../../shared/types'; import './App.css'; const App: React.FC = () => { const { user, handleLogin, handleRegister, handleLogout } = useAuth(); - const { game, fen, selectedPiece, moveHistory, evaluation, suggestedMove, makeAMove, onSquareClick, onPieceDrop, startNewGame, undoLastMove, setSuggestedMove } = useChessGame(); + const { + game, + fen, + selectedPiece, + moveHistory, + evaluation, + suggestedMove, + startNewGame, + undoLastMove, + setSuggestedMove + } = useChessGame(); const { opponent, setOpponent, searchDepth, setSearchDepth, setStockfishDepth } = useOpponent(); - const { ws, onlineUsers } = useWebSocket(user); + const { ws } = useWebSocket(user); + + const [onlineUsers, setOnlineUsers] = useState([]); + const [incomingChallenges, setIncomingChallenges] = useState([]); const boardSize = 600; + const startNewGameWithOpponent = useCallback((opponentUsername: string) => { + startNewGame(opponentUsername); + }, [startNewGame]); + + useEffect(() => { + if (!ws) return; + + ws.onmessage = (event: MessageEvent) => { + const data: WebSocketMessage = JSON.parse(event.data); + console.log('Received WebSocket message:', data); + + if (data.type === 'challenge_received') { + setIncomingChallenges(prev => [...prev, data.from]); + } + + if (data.type === 'challenge_response') { + if (data.accepted) { + alert(`${data.from} accepted your challenge! Starting new game.`); + startNewGameWithOpponent(data.from); + } else { + alert(`${data.from} rejected your challenge.`); + } + } + + if (data.type === 'start_game') { + alert(`Starting game with ${data.opponent}`); + startNewGameWithOpponent(data.opponent); + } + + if (data.type === 'onlineUsers') { + setOnlineUsers(data.users); + } + }; + + return () => { + ws.onmessage = null; + }; + }, [ws, setOnlineUsers, startNewGameWithOpponent]); + + const handleChallenge = (targetUsername: string) => { + if (!ws) return; + const challengeMessage: WebSocketMessage = { + type: 'challenge', + from: user?.username || '', + to: targetUsername + }; + ws.send(JSON.stringify(challengeMessage)); + alert(`Challenge sent to ${targetUsername}`); + }; + + const handleAcceptChallenge = (fromUsername: string) => { + if (!ws) return; + const responseMessage: WebSocketMessage = { + type: 'challenge_response', + from: user?.username || '', + to: fromUsername, + accepted: true + }; + ws.send(JSON.stringify(responseMessage)); + setIncomingChallenges(prev => prev.filter(username => username !== fromUsername)); + startNewGameWithOpponent(fromUsername); + }; + + const handleRejectChallenge = (fromUsername: string) => { + if (!ws) return; + const responseMessage: WebSocketMessage = { + type: 'challenge_response', + from: user?.username || '', + to: fromUsername, + accepted: false + }; + ws.send(JSON.stringify(responseMessage)); + setIncomingChallenges(prev => prev.filter(username => username !== fromUsername)); + alert(`Rejected challenge from ${fromUsername}`); + }; + const requestSuggestion = async () => { if (game.turn() === 'w') { try { @@ -51,6 +140,19 @@ const App: React.FC = () => { } }; + const onPieceDrop = (sourceSquare: string, targetSquare: string) => { + // Implement move logic here + // This function should interact with your useChessGame hook to make a move + // For example: + // makeAMove(sourceSquare, targetSquare); + // Ensure that your hooks provide the necessary functions + return true; + }; + + const onSquareClick = (square: string) => { + // Implement any additional logic for square clicks here + }; + return (

StockMate Chess

@@ -118,13 +220,30 @@ const App: React.FC = () => {

Online Players

{onlineUsers.length > 0 ? (
    - {onlineUsers.map((onlineUser) => ( -
  • {onlineUser.username}
  • - ))} + {onlineUsers + .filter((onlineUser: OnlineUser) => onlineUser.username !== user.username) + .map((onlineUser: OnlineUser) => ( +
  • + {onlineUser.username} + +
  • + ))}
) : (

No other players online

)} + {incomingChallenges.length > 0 && ( +
+

Incoming Challenges:

+ {incomingChallenges.map((challenger, index) => ( +
+ {challenger} has challenged you to a game. + + +
+ ))} +
+ )}
diff --git a/package-lock.json b/package-lock.json index f15d22c..5615789 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,14 @@ "@types/chess.js": "^0.13.7", "@types/uuid": "^10.0.0", "@types/ws": "^8.5.12", + "axios": "^1.7.7", "bcrypt": "^5.1.1", "chess.js": "^1.0.0-beta.8", "concurrently": "^6.2.0", + "cors": "^2.8.5", "express": "^4.17.1", "jsonwebtoken": "^9.0.2", + "node-uci": "^1.3.4", "nodemon": "^2.0.7", "path": "^0.12.7", "sqlite": "^5.1.1", @@ -26,6 +29,7 @@ }, "devDependencies": { "@types/bcrypt": "^5.0.2", + "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", "@types/jsonwebtoken": "^9.0.6", @@ -41,7 +45,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/highlight": "^7.24.7", @@ -55,7 +58,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -65,7 +67,6 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.24.7", @@ -81,7 +82,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" @@ -94,7 +94,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", @@ -109,7 +108,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" @@ -119,14 +117,12 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, "license": "MIT" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -365,6 +361,23 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -737,6 +750,23 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -810,6 +840,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -887,6 +923,12 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1108,6 +1150,24 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1210,6 +1270,31 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/core-js": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.2.tgz", + "integrity": "sha512-hIE5dXkRzRvnZ5vhkRfQxUvDxQZmD9oueA08jDYRBKJHx+VIl/Pne/e0A4x9LObEEthC/TqiZybUoNM4tRgnKg==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1284,6 +1369,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -1464,6 +1558,12 @@ "node": ">=8" } }, + "node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "license": "MIT" + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -1578,6 +1678,40 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1769,7 +1903,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2200,11 +2333,35 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "license": "MIT", + "dependencies": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/jsbn": { @@ -2412,6 +2569,12 @@ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", "license": "MIT" }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -2825,6 +2988,41 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/node-uci": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/node-uci/-/node-uci-1.3.4.tgz", + "integrity": "sha512-WeFuzceyHUniQO4XpKHjKFV4R+S63aJ/B/6kMO5ULh4KDeNbsBhzhtuuTNOczyy7dYwsjSoHyJjEocdVaecrBg==", + "license": "MIT", + "dependencies": { + "bluebird": "3.7.2", + "core-js": "3.6.2", + "debug": "4.1.1", + "lodash": "4.17.15", + "rollup-plugin-terser": "5.1.3" + } + }, + "node_modules/node-uci/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/node-uci/node_modules/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "license": "MIT" + }, + "node_modules/node-uci/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/nodemon": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", @@ -3013,7 +3211,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -3126,6 +3323,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -3274,6 +3477,60 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", + "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/node": "*", + "acorn": "^7.1.0" + }, + "bin": { + "rollup": "dist/bin/rollup" + } + }, + "node_modules/rollup-plugin-terser": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.1.3.tgz", + "integrity": "sha512-FuFuXE5QUJ7snyxHLPp/0LFXJhdomKlIx/aK7Tg88Yubsx/UU/lmInoJafXJ4jwVVNcORJ1wRUC5T9cy5yk0wA==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "jest-worker": "^24.6.0", + "rollup-pluginutils": "^2.8.1", + "serialize-javascript": "^2.1.2", + "terser": "^4.1.0" + }, + "peerDependencies": { + "rollup": ">=0.66.0 <2" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -3352,6 +3609,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/serialize-javascript": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", + "license": "BSD-3-Clause" + }, "node_modules/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", @@ -3564,6 +3827,25 @@ "license": "MIT", "optional": true }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", @@ -3709,7 +3991,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^3.0.0" @@ -3769,6 +4050,23 @@ "node": ">=6" } }, + "node_modules/terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "license": "BSD-2-Clause", + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index 509be92..81c039f 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,14 @@ "@types/chess.js": "^0.13.7", "@types/uuid": "^10.0.0", "@types/ws": "^8.5.12", + "axios": "^1.7.7", "bcrypt": "^5.1.1", "chess.js": "^1.0.0-beta.8", "concurrently": "^6.2.0", + "cors": "^2.8.5", "express": "^4.17.1", "jsonwebtoken": "^9.0.2", + "node-uci": "^1.3.4", "nodemon": "^2.0.7", "path": "^0.12.7", "sqlite": "^5.1.1", @@ -31,6 +34,7 @@ }, "devDependencies": { "@types/bcrypt": "^5.0.2", + "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", "@types/jsonwebtoken": "^9.0.6", diff --git a/server/src/index.ts b/server/src/index.ts index ff53093..df23a60 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -8,7 +8,7 @@ import jwt from 'jsonwebtoken'; import { v4 as uuidv4 } from 'uuid'; import { GetMoveRequest, GetMoveResponse, SuccessfulGetMoveResponse, ErrorResponse } from '../../shared/types'; import { NewGameRequest, NewGameResponse } from '../../shared/types'; -import { MoveResponse, MoveRequest } from '../../shared/types'; +import { MoveResponse, MoveRequest, WebSocketMessage } from '../../shared/types'; import { User, UserLoginRequest, UserRegistrationRequest, AuthResponse, RefreshTokenRequest } from '../../shared/types'; import { initializeDatabase } from './database'; import { createUser, getUser, updateElo } from './database/models/User'; @@ -40,6 +40,7 @@ const REFRESH_TOKEN_SECRET = 'your_refresh_token_secret'; // In-memory storage for refresh tokens and online users (replace with a database in production) let refreshTokens: string[] = []; const onlineUsers: { [key: string]: { id: string, username: string } } = {}; +const wsClients: { [username: string]: WebSocket } = {}; // Add a middleware to log all incoming requests app.use((req, res, next) => { @@ -348,7 +349,8 @@ app.post<{}, AuthResponse, UserLoginRequest>('/api/login', async (req, res) => { console.log('Tokens generated for user:', username); res.json({ success: true, accessToken, refreshToken, username: user.username, elo: user.elo_rating }); // Broadcast updated online users list - onlineUsers[req.body.username] = { id: user.id, username: user.username }; + onlineUsers[user.id] = { id: user.id, username: user.username }; + wsClients[user.username] = (req as any).ws; // Assume WebSocket is attached to request broadcastOnlineUsers(); } else { console.log('Login failed for user:', username); @@ -382,6 +384,7 @@ app.post('/api/logout', authenticateToken, (req: any, res) => { if (userIdToRemove) { console.log(`Removing user from online users: ${username}`); delete onlineUsers[userIdToRemove]; + delete wsClients[username]; broadcastOnlineUsers(); } else { console.log(`User not found in online users: ${username}`); @@ -512,24 +515,56 @@ wss.on('connection', (ws: WebSocket) => { ws.on('message', (message: string) => { console.log(`Received message: ${message}`); - const data = JSON.parse(message); + const data: WebSocketMessage = JSON.parse(message); if (data.type === 'login' && typeof data.username === 'string') { console.log(`User logging in: ${data.username}`); if (username) { console.log(`Removing existing user: ${username}`); - delete onlineUsers[userId]; + delete onlineUsers[username]; + delete wsClients[username]; } username = data.username; - onlineUsers[userId] = { id: userId, username: data.username }; - console.log(`Added user to online users: ${JSON.stringify(onlineUsers[userId])}`); + onlineUsers[username] = { id: userId, username: data.username }; + wsClients[username] = ws; + console.log(`Added user to online users: ${JSON.stringify(onlineUsers[username])}`); broadcastOnlineUsers(); - } else if (data.type === 'logout') { - console.log(`WebSocket logout message received for user: ${username}`); - if (username) { - console.log(`Removing user from online users: ${username}`); - delete onlineUsers[userId]; - username = null; - broadcastOnlineUsers(); + } else if (data.type === 'challenge' && data.from && data.to) { + console.log(`Challenge from ${data.from} to ${data.to}`); + const targetWs = wsClients[data.to]; + if (targetWs && targetWs.readyState === WebSocket.OPEN) { + const challengeReceived: WebSocketMessage = { + type: 'challenge_received', + from: data.from + }; + targetWs.send(JSON.stringify(challengeReceived)); + console.log(`Sent challenge_received to ${data.to}`); + } else { + console.log(`User ${data.to} is not online`); + } + } else if (data.type === 'challenge_response' && data.from && data.to !== undefined) { + console.log(`Challenge response from ${data.from} to ${data.to}: ${data.accepted}`); + const challengerWs = wsClients[data.to]; + if (challengerWs && challengerWs.readyState === WebSocket.OPEN) { + const challengeResponse: WebSocketMessage = { + type: 'challenge_response', + from: data.from, + to: data.to, + accepted: data.accepted + }; + challengerWs.send(JSON.stringify(challengeResponse)); + console.log(`Sent challenge_response to ${data.to}`); + + if (data.accepted) { + const startGame: WebSocketMessage = { + type: 'start_game', + opponent: data.from + }; + challengerWs.send(JSON.stringify(startGame)); + ws.send(JSON.stringify(startGame)); + console.log(`Started game between ${data.from} and ${data.to}`); + } + } else { + console.log(`Challenger ${data.to} is not online`); } } }); @@ -538,7 +573,8 @@ wss.on('connection', (ws: WebSocket) => { console.log(`WebSocket connection closed. UserId: ${userId}`); if (username) { console.log(`Removing user from online users: ${username}`); - delete onlineUsers[userId]; + delete onlineUsers[username]; + delete wsClients[username]; broadcastOnlineUsers(); } }); diff --git a/shared/types.ts b/shared/types.ts index 57f0231..e7fa120 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -91,3 +91,30 @@ export interface OnlineUser { id: string; username: string; } + +/* WebSocket Messages */ + +export type WebSocketMessage = + | { + type: 'login'; + username: string; + } + | { + type: 'challenge'; + from: string; + to: string; + } + | { + type: 'challenge_received'; + from: string; + } + | { + type: 'challenge_response'; + from: string; + to: string; + accepted: boolean; + } + | { + type: 'start_game'; + opponent: string; + };