diff --git a/ChromeExtension/Backend/controllers/gameController.js b/ChromeExtension/Backend/controllers/gameController.js index 35c54b8..00272dd 100644 --- a/ChromeExtension/Backend/controllers/gameController.js +++ b/ChromeExtension/Backend/controllers/gameController.js @@ -1,76 +1,144 @@ import Game from '../models/gameModel.js'; -import User from '../models/userModel.js'; -import InvitationCode from '../models/InvitationCodeModel.js'; +// import { v4 as uuidv4 } from 'uuid'; // Get all games -const getAllGames = async (req, res) => { +export const getAllGames = async (req, res) => { try { - const games = await Game.find().populate('player_1_id player_2_id invitation_code_id'); + const games = await Game.find(); // Removed .populate() res.status(200).json(games); } catch (err) { - res.status(500).json({ message: err.message }); + console.error("Error fetching games:", err); + res.status(500).json({ message: "Failed to retrieve games", error: err.message }); } }; -// Get one game by ID -const getGameById = async (req, res) => { +// Create a new game +export const createGame = async (req, res) => { try { - const game = await Game.findById(req.params.id).populate('player_1_id player_2_id invitation_code_id'); - if (!game) return res.status(404).json({ message: 'Game not found' }); + const { invitation_code, username } = req.body; - res.status(200).json(game); - } catch (err) { - res.status(500).json({ message: err.message }); + if (!invitation_code) { + return res.status(400).json({ message: 'invitation_code is required' }); + } + + // Check if invitation_code already exists + const existingGame = await Game.findOne({ invitation_code }); + if (existingGame) { + return res.status(400).json({ message: 'Invitation code already in use' }); + } + + const newGame = new Game({ + invitation_code, + player_1: username + }); + + await newGame.save(); + res.status(201).json(newGame); + } catch (error) { + res.status(500).json({ message: 'Error creating game', error }); } }; -// Create a new game -const createGame = async (req, res) => { - const { player_1_id, player_2_id, invitation_code } = req.body; +// Join a game +export const joinGame = async (req, res) => { + try { + const { invitation_code, username } = req.body; + + if (!invitation_code) { + return res.status(400).json({ message: 'invitation_code is required' }); + } + + const game = await Game.findOne({ invitation_code }); + + if (!game) { + return res.status(400).json({ message: 'Invalid invitation code' }); + } + + if (game.player_2) { + return res.status(400).json({ message: 'Player 2 already joined' }); + } + + if (game.player_1 === username) { + return res.status(400).json({ message: 'You are already in this game as Player 1' }); + } + + game.player_2 = username; + game.status = 'paired'; + await game.save(); + res.status(200).json(game); + } catch (error) { + res.status(500).json({ message: 'Error joining game', error }); + } +}; + +// Update game +export const updateGame = async (req, res) => { try { - const invitation = await InvitationCode.findOne({ invitation_code, status: 'active' }); - if (!invitation) { - return res.status(400).json({ message: 'Invalid or inactive invitation code' }); + const gameId = req.params.id; + const { player_1, player_2, invitation_code, status } = req.body; + + const game = await Game.findById(gameId); + if (!game) { + return res.status(404).json({ message: 'Game not found' }); } - const newGame = new Game({ - player_1_id, - player_2_id, - invitation_code_id: invitation._id - }); + // Merge any fields provided in the request + if (player_1 !== undefined) game.player_1 = player_1; + if (player_2 !== undefined) game.player_2 = player_2; + if (invitation_code !== undefined) game.invitation_code = invitation_code; + if (status !== undefined) game.status = status; - await newGame.save(); + await game.save(); + res.status(200).json(game); + } catch (error) { + console.error('Error updating game:', error); + res.status(500).json({ message: 'Failed to update game', error }); + } +}; - invitation.status = 'inactive'; - await invitation.save(); - res.status(201).json(newGame); - } catch (err) { - res.status(500).json({ message: err.message }); +// Update game status +export const updateGameStatus = async (req, res) => { + try { + const { status } = req.body; // Expect new status in body + const game = await Game.findById(req.params.id); + + if (!game) { + return res.status(404).json({ message: 'Game not found' }); + } + + game.status = status; // Update the game status + await game.save(); + res.status(200).json(game); + } catch (error) { + res.status(500).json({ message: 'Error updating game status' }); } }; -// Delete one game by ID -const deleteGameById = async (req, res) => { +// Get a game by invitation code +export const getGameByInvitationCode = async (req, res) => { try { - const deletedGame = await Game.findByIdAndDelete(req.params.id); - if (!deletedGame) return res.status(404).json({ message: 'Game not found' }); + const { invitation_code_id } = req.params; // Get the invitation code from URL params + const game = await Game.findOne({ invitation_code_id }).populate('player_1 player_2'); - res.status(200).json({ message: 'Game deleted successfully' }); - } catch (err) { - res.status(500).json({ message: err.message }); + if (!game) { + return res.status(404).json({ message: 'Game not found' }); + } + + res.status(200).json(game); // Return the game details + } catch (error) { + res.status(500).json({ message: 'Error retrieving game by invitation code' }); } }; // Delete all games -const deleteAllGames = async (req, res) => { +export const deleteAllGames = async (req, res) => { try { - await Game.deleteMany(); - res.status(200).json({ message: 'All games deleted successfully' }); - } catch (err) { - res.status(500).json({ message: err.message }); + const result = await Game.deleteMany({}); + res.status(200).json({ message: `Deleted ${result.deletedCount} games successfully.` }); + } catch (error) { + console.error("Error deleting games:", error); + res.status(500).json({ message: "Failed to delete games", error: error.message }); } }; - -export { getAllGames, getGameById, createGame, deleteGameById, deleteAllGames }; diff --git a/ChromeExtension/Backend/controllers/invitationCodeController.js b/ChromeExtension/Backend/controllers/invitationCodeController.js deleted file mode 100644 index 47ef79c..0000000 --- a/ChromeExtension/Backend/controllers/invitationCodeController.js +++ /dev/null @@ -1,66 +0,0 @@ -import generateRandomCode from '../utils/code_generator.js'; -import InvitationCode from "../models/InvitationCodeModel.js"; - -// Create a new invitation code -const createInvitation = async (req, res) => { - const invitation_code = generateRandomCode(); - - const existingCode = await InvitationCode.findOne({ invitation_code }); - if (existingCode) { - return res.status(400).json({ error: 'Invitation code already exists.' }); - } - - try { - const newInvitation = new InvitationCode({ invitation_code }); - await newInvitation.save(); - res.status(201).json(newInvitation); - } catch (err) { - res.status(500).json({ message: err.message }); - } -}; - -// Get all invitation codes -const getAllInvitations = async (req, res) => { - try { - const invitations = await InvitationCode.find(); - res.status(200).json(invitations); - } catch (err) { - res.status(500).json({ message: err.message }); - } -}; - -// Get one invitation code by ID -const getInvitationById = async (req, res) => { - try { - const invitation = await InvitationCode.findById(req.params.id); - if (!invitation) return res.status(404).json({ message: 'Invitation code not found' }); - - res.status(200).json(invitation); - } catch (err) { - res.status(500).json({ message: err.message }); - } -}; - -// Delete one invitation code by ID -const deleteInvitationById = async (req, res) => { - try { - const deletedInvitation = await InvitationCode.findByIdAndDelete(req.params.id); - if (!deletedInvitation) return res.status(404).json({ message: 'Invitation code not found' }); - - res.status(200).json({ message: 'Invitation code deleted successfully' }); - } catch (err) { - res.status(500).json({ message: err.message }); - } -}; - -// Delete all invitation codes -const deleteAllInvitations = async (req, res) => { - try { - await InvitationCode.deleteMany(); - res.status(200).json({ message: 'All invitation codes deleted successfully' }); - } catch (err) { - res.status(500).json({ message: err.message }); - } -}; - -export { getAllInvitations, getInvitationById, createInvitation, deleteInvitationById, deleteAllInvitations }; diff --git a/ChromeExtension/Backend/controllers/userController.js b/ChromeExtension/Backend/controllers/userController.js index 65c500d..b1b580a 100644 --- a/ChromeExtension/Backend/controllers/userController.js +++ b/ChromeExtension/Backend/controllers/userController.js @@ -1,18 +1,10 @@ import User from '../models/userModel.js'; -import { validateUser } from '../utils/leetcodeGraphQLQueries.js'; // Create a new user const createUser = async (req, res) => { const { username } = req.body; try { - // Validate Username - const validUser = await validateUser(username); - console.log(username) - if (!validUser) { - return res.status(400).json({ message: 'Invalid LeetCode username.' }); - } - const newUser = new User({ username }); await newUser.save(); res.status(201).json(newUser); diff --git a/ChromeExtension/Backend/models/gameModel.js b/ChromeExtension/Backend/models/gameModel.js index 1007ca0..eb54bd6 100644 --- a/ChromeExtension/Backend/models/gameModel.js +++ b/ChromeExtension/Backend/models/gameModel.js @@ -1,32 +1,47 @@ import mongoose from 'mongoose'; const GameSchema = new mongoose.Schema({ - invitation_code_id: { - type: mongoose.Schema.Types.ObjectId, - ref: 'InvitationCode', + invitation_code: { + type: String, default: null }, - player_1_id: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User', + player_1: { + // type: mongoose.Schema.Types.ObjectId, + type: String, + // ref: 'User', default: null }, - player_2_id: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User', + player_2: { + type: String, + // type: mongoose.Schema.Types.ObjectId, + // ref: 'User', default: null }, winner: { - type: String, - enum: ['player_1', 'player_2', 'draw', null], + // type: mongoose.Schema.Types.ObjectId, + // ref: 'User', + type:String, default: null }, + status: { + type: String, + enum: ['waiting', 'paired', 'in_progress', 'completed'], + default: 'waiting' + }, + code_expires_at: { + type: Date, + default: () => Date.now() + 15 * 60 * 1000 // Expires in 15 minutes + }, + score: { + player_1: { type: Number, default: 0 }, + player_2: { type: Number, default: 0 } + }, date: { type: Date, default: null } -}); +}, { timestamps: true }); const Game = mongoose.model('Game', GameSchema); -export default Game; +export default Game; diff --git a/ChromeExtension/Backend/models/invitationCodeModel.js b/ChromeExtension/Backend/models/invitationCodeModel.js deleted file mode 100644 index 3efeae6..0000000 --- a/ChromeExtension/Backend/models/invitationCodeModel.js +++ /dev/null @@ -1,21 +0,0 @@ -import mongoose from 'mongoose'; - -const InvitationCodeSchema = new mongoose.Schema({ - invitation_code: { - type: String, - required: true - }, - status: { - type: String, - enum: ['active', 'inactive'], - default: 'active' - }, - date: { - type: Date, - default: Date.now - } -}); - -const InvitationCode = mongoose.model('InvitationCode', InvitationCodeSchema); - -export default InvitationCode; diff --git a/ChromeExtension/Backend/models/userModel.js b/ChromeExtension/Backend/models/userModel.js index 59d5031..1f7f862 100644 --- a/ChromeExtension/Backend/models/userModel.js +++ b/ChromeExtension/Backend/models/userModel.js @@ -12,7 +12,11 @@ const UserSchema = new mongoose.Schema({ gamesPlayed: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Game' - }] + }], + // elo: { + // type: Float32Array, + // default: + // } }); const User = mongoose.model('User', UserSchema); diff --git a/ChromeExtension/Backend/package.json b/ChromeExtension/Backend/package.json index 405cbe9..82338b5 100644 --- a/ChromeExtension/Backend/package.json +++ b/ChromeExtension/Backend/package.json @@ -17,7 +17,9 @@ "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", - "mongoose": "^8.10.1" + "mongoose": "^8.10.1", + "uuid": "^11.1.0", + "ws": "^8.18.1" }, "devDependencies": { "@babel/core": "^7.26.9", diff --git a/ChromeExtension/Backend/routes/gameRoutes.js b/ChromeExtension/Backend/routes/gameRoutes.js index 7b5633c..6233dc2 100644 --- a/ChromeExtension/Backend/routes/gameRoutes.js +++ b/ChromeExtension/Backend/routes/gameRoutes.js @@ -1,13 +1,14 @@ import express from 'express'; -import { getAllGames, getGameById, createGame, deleteGameById, deleteAllGames } from '../controllers/gameController.js'; +import { getAllGames, createGame, joinGame, updateGame, deleteAllGames, updateGameStatus } from '../controllers/gameController.js'; const router = express.Router(); // Game Routes -router.get('/games', getAllGames); -router.get('/games/:id', getGameById); -router.post('/games', createGame); -router.delete('/games/:id', deleteGameById); -router.delete('/games', deleteAllGames); +router.get('/', getAllGames); +router.post('/', createGame); +router.post('/join', joinGame); +router.patch('/:id', updateGame); +router.patch('/:id/status', updateGameStatus); +router.delete('/', deleteAllGames); -export default router; +export default router; \ No newline at end of file diff --git a/ChromeExtension/Backend/routes/invitationRoutes.js b/ChromeExtension/Backend/routes/invitationRoutes.js deleted file mode 100644 index 92aad82..0000000 --- a/ChromeExtension/Backend/routes/invitationRoutes.js +++ /dev/null @@ -1,13 +0,0 @@ -import express from 'express'; -import { createInvitation, getInvitationById, getAllInvitations, deleteAllInvitations, deleteInvitationById } from '../controllers/invitationCodeController.js'; - -const router = express.Router(); - -// Invitation Code Routes -router.get('/all', getAllInvitations); -router.get('/:id', getInvitationById); -router.post('/', createInvitation); -router.delete('/all', deleteAllInvitations); -router.delete('/:id', deleteInvitationById); - -export default router; diff --git a/ChromeExtension/Backend/server.js b/ChromeExtension/Backend/server.js index caafc65..ac7cc64 100644 --- a/ChromeExtension/Backend/server.js +++ b/ChromeExtension/Backend/server.js @@ -1,18 +1,51 @@ import express from 'express'; import bodyParser from 'body-parser'; +import http from 'http'; +import { WebSocketServer } from 'ws'; import connectDB from './config/db.js'; import gameRoutes from './routes/gameRoutes.js'; -import invitationRoutes from './routes/invitationRoutes.js'; import userRoutes from './routes/userRoutes.js'; import { fetchRecentSubmissions } from './utils/leetcodeGraphQLQueries.js'; import { validateUser } from './utils/leetcodeGraphQLQueries.js'; import { deleteAllUsers } from './controllers/userController.js'; const app = express(); -app.get("/", (_, res) => res.json({ message: "Welcome to Yeetcode API" })); +const server = http.createServer(app); +const wss = new WebSocketServer({ server, path: "/ws" }); -// Port -const port = process.env.PORT || 3000; +// Store connected clients +let clients = {}; + +// WebSocket connection handling +wss.on("connection", (ws) => { + console.log("New WebSocket connection"); + + ws.on("message", (message) => { + const data = JSON.parse(message); + + if (data.type === "CREATE_GAME") { + clients[data.gameId] = ws; + } + + if (data.type === "PLAYER_JOINED") { + if (clients[data.gameId]) { + clients[data.gameId].send(JSON.stringify(data)); + } + } + + if (data.type === "START_GAME") { + if (clients[data.gameId]) { + clients[data.gameId].send(JSON.stringify(data)); + } + } + }); + + ws.on("close", () => { + console.log("WebSocket closed"); + }); +}); + +app.get("/", (_, res) => res.json({ message: "Welcome to Yeetcode API" })); // Middleware app.use(bodyParser.json()); @@ -22,7 +55,6 @@ connectDB(); // Routes app.use('/api/users', userRoutes); app.use('/api/games', gameRoutes); -app.use('/api/invitationCodes', invitationRoutes); // @@ -75,7 +107,7 @@ app.post('/api/validateUser', async (req, res) => { }); - -app.listen(port, () => { +const port = process.env.PORT || 3000; +server.listen(port, () => { console.log(`Server running on port ${port}`); -}); +}); \ No newline at end of file diff --git a/ChromeExtension/Frontend/assets/js/background.js b/ChromeExtension/Frontend/assets/js/background.js index 3984cf6..4a97e52 100644 --- a/ChromeExtension/Frontend/assets/js/background.js +++ b/ChromeExtension/Frontend/assets/js/background.js @@ -1,21 +1,45 @@ +const socket = new WebSocket("ws://localhost:3000/ws"); + +socket.onopen = () => { + console.log("WebSocket connected."); +}; + +socket.onmessage = (event) => { + const data = JSON.parse(event.data); + + if (data.type === "PLAYER_JOINED") { + chrome.runtime.sendMessage({ type: "UPDATE_PLAYER_2", player2: data.player2, gameId: data.gameId }); + } + + if (data.type === "START_GAME") { + chrome.tabs.query({}, (tabs) => { + for (let tab of tabs) { + chrome.tabs.sendMessage(tab.id, { type: "START_GAME" }); + } + }); + } +}; + +// Listen for messages from popup.js +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (["CREATE_GAME", "PLAYER_JOINED", "START_GAME"].includes(message.type)) { + socket.send(JSON.stringify(message)); + } + sendResponse({ status: "ok" }); +}); + chrome.runtime.onInstalled.addListener(() => { chrome.sidePanel.setOptions({ path: "Frontend/main-screen.html" }) chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true}); }); -chrome.tabs.onUpdated.addListener((tabId, tab) => { - console.log("\n\nTesting: BACKGROUND LOGS!!!\n\n") - chrome.tabs.sendMessage(tabId, { - message: "connected" - }) - }) - -// //anti cheat functions -// chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { -// if(chrome.runtime.lastError) { -// console.error(chrome.runtime.lastError.message); -// return; -// } + //anti cheat functions +chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + if(chrome.runtime.lastError) { + console.error(chrome.runtime.lastError.message); + return; + } +}) // console.log("THIS IS THE TAB URL: " + tab.url) // if (tab.url && tab.url.includes("https://chatgpt.com/")) { diff --git a/ChromeExtension/Backend/utils/code_generator.js b/ChromeExtension/Frontend/assets/js/code_generator.js similarity index 60% rename from ChromeExtension/Backend/utils/code_generator.js rename to ChromeExtension/Frontend/assets/js/code_generator.js index b8f1a4b..f79376e 100644 --- a/ChromeExtension/Backend/utils/code_generator.js +++ b/ChromeExtension/Frontend/assets/js/code_generator.js @@ -2,12 +2,12 @@ const letters = "001122334455667778899abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP const numLetters = letters.length; const generateRandomCode = () => { - var code = ""; - for (var i=0; i<4; i++) { - let idx = Math.floor(Math.random()*numLetters); + let code = ""; + for (let i = 0; i < 4; i++) { + let idx = Math.floor(Math.random() * numLetters); code += letters.charAt(idx); } return code; -} +}; -export default generateRandomCode; \ No newline at end of file +export default generateRandomCode; diff --git a/ChromeExtension/Frontend/assets/js/popup.js b/ChromeExtension/Frontend/assets/js/popup.js index bc2d25e..7d7ebf5 100644 --- a/ChromeExtension/Frontend/assets/js/popup.js +++ b/ChromeExtension/Frontend/assets/js/popup.js @@ -1,83 +1,257 @@ -// Leetcode user API functionality -//import getUserData from "./../../../Backend/leetcode_user.js"; -import { validateUser } from "./../../../Backend/utils/validateUserGraphQL.js"; -import generateRandomCode from "../../../Backend/utils/code_generator.js"; +import generateRandomCode from "./code_generator.js"; -document.addEventListener("DOMContentLoaded", function () { +const socket = new WebSocket("ws://localhost:3000/ws"); - // Only attach event listener if the button exists on the current page +document.addEventListener("DOMContentLoaded", function () { + // UI Elements + const createTeamButton = document.getElementById("create-team-button"); + const joinTeamButton = document.getElementById("join-team-button"); + const startGameButton = document.getElementById("start-game-button"); + const copyCodeButton = document.getElementById("copyCode"); + const inviteCodeElement = document.getElementById("inviteCode"); + let player1Input = document.getElementById("player1Name"); + const player2Container = document.getElementById("player2-container"); + let player2Input = document.getElementById("player2Name"); + const confirmJoinButton = document.getElementById("confirm-join"); let back_to_main_button = document.getElementById("back-to-main-screen"); + let back_to_main_button_from_join = document.getElementById("back-to-main-screen-from-join"); + + // Navigate back to main screen if (back_to_main_button) { back_to_main_button.addEventListener("click", function () { - window.location.href = "main-screen.html"; // Navigate back to main screen + window.location.href = "main-screen.html"; }); } - let back_to_main_button_from_join = document.getElementById("back-to-main-screen-from-join"); + // Navigate back to main screen if (back_to_main_button_from_join) { back_to_main_button_from_join.addEventListener("click", function () { - window.location.href = "main-screen.html"; // Navigate back to main screen + window.location.href = "main-screen.html"; + }); + } + + let gameId; + let isPlayer2 = false; + + // Fetch game state per extension instance + chrome.storage.local.get(["gameId", "isPlayer2"], (data) => { + if (data.gameId) { + gameId = data.gameId; + isPlayer2 = data.isPlayer2; + } else { + console.error("Game ID not found in storage."); + } + + // If Player 2 is waiting, start polling for game status + if (isPlayer2 && gameId) { + console.log("Polling for game status..."); + if (!gameId) { + console.error("Game ID is missing."); + return; + } + const pollInterval = setInterval(() => { + fetch(`http://localhost:3000/api/games/${gameId}`) + .then(response => response.json()) + .then(game => { + console.log("Game Status:", game.status); + if (game.status === "in_progress") { + clearInterval(pollInterval); + console.log("Game started! Redirecting Player 2..."); + window.location.href = "game-play-screen.html"; + } + }) + .catch(err => console.error("Error polling game status:", err)); + }, 1500); + } + }); + + // Toggle button state + const toggleButtonState = (button, isEnabled) => { + if (button) { + button.disabled = !isEnabled; + button.style.backgroundColor = isEnabled ? "#eda93a" : "#555"; // Green active, dark grey disabled + button.style.cursor = isEnabled ? "pointer" : "not-allowed"; + } + }; + + // **Create Team Flow (Player 1)** + if (createTeamButton) { + createTeamButton.addEventListener("click", () => { + localStorage.clear(); + chrome.storage.local.clear(); + window.location.href = "create-team-screen.html"; }); } - let create_team_button = document.getElementById("create-team-button"); - if (create_team_button) { - create_team_button.addEventListener("click", function () { - const gameCode = generateRandomCode(); - localStorage.setItem("gameCode", gameCode); - window.location.href = "create-team-screen.html"; // Navigate to Create Team page + if (inviteCodeElement) { + chrome.storage.local.get(["inviteCode"], (data) => { + let code = data.inviteCode || generateRandomCode(); + inviteCodeElement.innerText = code; + chrome.storage.local.set({ inviteCode: code }); + + toggleButtonState(startGameButton, false); + + if (!gameId) { + // Create game on backend + fetch("http://localhost:3000/api/games", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ invitation_code: code, username: null }) + }) + .then(response => response.json()) + .then(data => { + gameId = data._id; + chrome.storage.local.set({ gameId }); + socket.send(JSON.stringify({ type: "CREATE_GAME", gameId, invitation_code: code })); + }) + .catch(error => console.error("Error creating game:", error)); + } }); } + // **Copy Code Button** + if (copyCodeButton) { + copyCodeButton.addEventListener("click", () => { + chrome.storage.local.get(["inviteCode"], (data) => { + navigator.clipboard.writeText(data.inviteCode).then(() => { + alert("Code copied!"); + }); + }); + }); + } - let join_team_button = document.getElementById("join-team-button"); - if (join_team_button) { - join_team_button.addEventListener("click", function () { - window.location.href = "join-team-screen.html"; // Navigate to Join Team page + // **Join Team Flow (Player 2)** + if (joinTeamButton) { + joinTeamButton.addEventListener("click", () => { + window.location.href = "join-team-screen.html"; }); } - let start_game_button = document.getElementById("start-game-button"); - - if (start_game_button) { - start_game_button.addEventListener("click", async function () { - //When the start button gets clicked on, the backend checks whether - //the two inputted users are actually valid. - //If a player is invalid, then the text box will notify it. - const player1Input = document.getElementById("player1Name"); - const player2Input = document.getElementById("player2Name"); + if (confirmJoinButton) { + confirmJoinButton.addEventListener("click", () => { + const codeInput = document.getElementById("teamCodeInput").value.trim(); + const player2Name = document.getElementById("player2Name").value.trim(); - const player1Name = player1Input.value; - const player2Name = player2Input.value; + fetch("http://localhost:3000/api/games/join", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ invitation_code: codeInput, username: player2Name }) + }) + .then(res => res.json()) + .then(data => { + if (data.message) { + alert(data.message); + } else { + chrome.storage.local.set({ gameId: data._id, isPlayer2: true, player2: player2Name }); - let isValid1 = await validateUser(player1Name); - let isValid2 = await validateUser(player2Name); + socket.send(JSON.stringify({ + type: "PLAYER_JOINED", + gameId: data._id, + invitation_code: codeInput, + player2: player2Name + })); - if(isValid1 && isValid2) { + console.log("Player 2 joined, polling for game start..."); + } + }) + .catch(err => console.error("Failed to join game:", err)); + }); + } + + async function getInviteCode() { + return new Promise((resolve) => { + chrome.storage.local.get(["inviteCode"], (data) => resolve(data.inviteCode)); + }); + } + + // **Listen for WebSocket updates** + socket.onmessage = async (event) => { + const data = JSON.parse(event.data); + const inviteCode = await getInviteCode(); + + if (data.type === "PLAYER_JOINED" && data.invitation_code === inviteCode) { + chrome.storage.local.get(["inviteCode"], (storage) => { + if (data.invitation_code === storage.inviteCode) { + console.log("Player 2 has joined! Updating UI..."); + + if (!player2Container) { + player2Container = document.createElement("div"); + player2Container.id = "player2-container"; + document.getElementById("create-team-screen").appendChild(player2Container); + } + + if (!player2Input) { + player2Input = document.createElement("input"); + player2Input.id = "player2Name"; + player2Input.placeholder = "Player 2"; // Default placeholder + player2Container.appendChild(player2Input); + } + + // Update Player 2's name properly + player2Input.value = data.player2 || ""; + player2Input.disabled = true; // Ensure Player 1 can't edit Player 2's name + + // Hide "Waiting for Player 2..." message + const waitingMsg = document.getElementById("waitingMsg"); + if (waitingMsg) waitingMsg.remove(); + + player2Container.style.display = "block"; + toggleButtonState(startGameButton, true); + + } + }); + } + + // Notify Player 2 when game starts + if (data.type === "START_GAME") { + console.log("Game started! Redirecting Player 2..."); + window.location.href = "game-play-screen.html"; + } + }; + + // **Start Game Button** + if (startGameButton) { + startGameButton.addEventListener("click", async () => { + try { + // Retrieve player names + const player1Name = player1Input.value.trim(); + const player2Name = player2Input.value.trim(); + + // Store in localStorage localStorage.setItem("Player1", player1Name); localStorage.setItem("Player2", player2Name); - window.location.href = "game-play-screen.html"; // Navigate to Join Team page - } - - else if (isValid1 === true && isValid2 === false) { - player1Input.style.border = "2px solid green"; - player2Input.style.border = "2px solid red"; - start_game_button = false; - } - - else if(isValid1 === false && isValid2 === true) { - player1Input.style.border = "2px solid red"; - player2Input.style.border = "2px solid green"; - start_game_button = false; - } - - else { - player1Input.style.border = "2px solid red"; - player2Input.style.border = "2px solid red"; - start_game_button = false; - } + // Validate players asynchronously + let [validPlayer1, validPlayer2] = await Promise.all([true, true]); + + if (validPlayer1 && validPlayer2) { + // Update game status on the backend + await fetch(`http://localhost:3000/api/games/${gameId}/status`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ status: "in_progress", player_1: player1Name }) + }); + // Notify other players via WebSocket + socket.send(JSON.stringify({ type: "START_GAME", gameId })); + + // Navigate to the gameplay screen + window.location.href = "game-play-screen.html"; + } else { + // Handle invalid players + console.log(`Is ${player1Name} a valid Leetcode user? ${validPlayer1}`); + console.log(`Is ${player2Name} a valid Leetcode user? ${validPlayer2}`); + + if (!validPlayer1) { + player1Input.value = `${player1Name} is not a Leetcode username. YEET!`; + } + if (!validPlayer2) { + player2Input.value = `${player2Name} is not a Leetcode username. YEET!`; + } + } + } catch (err) { + console.error("Failed to start game:", err); + } }); } @@ -89,7 +263,7 @@ document.addEventListener("DOMContentLoaded", function () { //to the list of currently available games } - const toggleCheckbox = document.getElementById("toggle-note-checkbox"); + const toggleCheckbox = document.getElementById("toggle-note-checkbox"); const noteContent = document.getElementById("note-content"); function adjustNoteHeight() { @@ -106,6 +280,4 @@ document.addEventListener("DOMContentLoaded", function () { // Update height when the window is resized window.addEventListener("resize", adjustNoteHeight); - - -}); \ No newline at end of file +}); diff --git a/ChromeExtension/Frontend/assets/js/utils.js b/ChromeExtension/Frontend/assets/js/utils.js new file mode 100644 index 0000000..c5f71d3 --- /dev/null +++ b/ChromeExtension/Frontend/assets/js/utils.js @@ -0,0 +1,5 @@ +// Save data to Chrome local storage +export function saveSession(data) { + chrome.storage.local.set(data, () => { + }); +} \ No newline at end of file diff --git a/ChromeExtension/Frontend/create-team-screen.html b/ChromeExtension/Frontend/create-team-screen.html index 6b2ce34..69d6c8f 100644 --- a/ChromeExtension/Frontend/create-team-screen.html +++ b/ChromeExtension/Frontend/create-team-screen.html @@ -11,32 +11,31 @@ -
- -
- - -

Invite Code: or4L

- -
- - -
- -
- - -
- - - +
+ + +

Invite Code: or4L +

+ +
+ + +
+ + - - - + +

Waiting for Player 2 to join...

+ + +
+ + \ No newline at end of file diff --git a/ChromeExtension/Frontend/join-team-screen.html b/ChromeExtension/Frontend/join-team-screen.html index 8cec301..c0cc80b 100644 --- a/ChromeExtension/Frontend/join-team-screen.html +++ b/ChromeExtension/Frontend/join-team-screen.html @@ -11,23 +11,19 @@ -
+
-
+ - - -

Got a team code? Paste it here!

- - - - - -
+ + + + -
+ +
diff --git a/ChromeExtension/manifest.json b/ChromeExtension/manifest.json index 25b8fb7..8cba090 100644 --- a/ChromeExtension/manifest.json +++ b/ChromeExtension/manifest.json @@ -23,7 +23,7 @@ "web_accessible_resources": [ { - "resources": ["Frontend/js/contentScript.js"], + "resources": ["Frontend/js/*.js"], "matches": [""] } ],