import { generate_keypair, generate_rsa_keypair } from "../crypto/main.js"; import { Random } from "./random.js"; import { Barrier } from "./barrier.js"; import { Packet } from "./packet.js"; import { Game } from "./game.js"; import { Region } from "./map.js"; import "./dom.js"; export const ID = window.crypto.randomUUID(); export const game = new Game(); export let socket; export let random; let barrier; window.paillier = generate_keypair(); window.rsa = generate_rsa_keypair(); // Not totally reliable but better than nothing. window.addEventListener("beforeunload", () => { socket.emit("message", Packet.createDisconnect()); }); document.addEventListener("DOMContentLoaded", () => { socket = io(); random = new Random(); barrier = new Barrier(); socket.on("connect", () => { window.console.log("Connected!"); window.console.log(`We are: ${ID}`); socket.emit("message", Packet.createAnnounce(window.rsa)); game.addPlayer(ID, true, window.rsa.pubKey); }); socket.on("message", async (packet) => { let data = packet.payload; window.console.log(data); const sender = game.players[data.author]; if (sender === undefined) { window.console.warn( `Not able to verify signature on packet ${data.id} as the sender is unknown.` ); } else { let sig = BigInt(packet.sig); // decrypt and compare signature let dehash = sender.rsaPubKey.encrypt(sig); let hash = BigInt("0x" + CryptoJS.SHA3(JSON.stringify(data)).toString()); if (dehash !== hash) { window.console.error(`Signature invalid! Ignoring packet ${data.id}.`); return; } // check if the packet is replayed. if (data.timestamp <= sender.lastPacket) { window.console.error(`Replay detected! Ignoring packet ${data.id}.`); return; } sender.lastPacket = data.timestamp; } switch (data.type) { case "RANDOM": if (data.author === ID) { return; } await random.processCooperativeRandom(data); break; case "BARRIER": if (data.author === ID) { return; } barrier.resolve(data); break; default: const event = new CustomEvent(data.type, { detail: data }); document.dispatchEvent(event); break; } }); }); /** * Process player connect packets: these inform that a new player has joined. * * @param data Packet received */ document.addEventListener("ANNOUNCE", (ev) => { const data = ev.detail; if (data.author === ID) return; let is_new = game.addPlayer(data.author, false, data.rsaPubKey); // When a new player is seen, all announce to ensure they know all players. if (is_new) { socket.emit("message", Packet.createAnnounce(window.rsa)); } }); document.addEventListener("DISCONNECT", (ev) => { const data = ev.detail; game.removePlayer(data.author); }); /** * Process keep-alive packets: these are packets that check players are still online. * * @param data Packet received */ document.addEventListener("KEEPALIVE", (ev) => { const data = ev.detail; game.keepAlive(data.author); }); document.addEventListener("ACT", async (ev) => { const data = ev.detail; if (game.isWaiting()) { game.setReady(data.author, data.ready); } else { if (data.author !== game.currentPlayer().id) { if (data.action === "DEFENSE") { await game.players[data.author].setDefense(data.amount); } return; } if (game.isPregame()) { if (!Region.allRegionsClaimed()) { // Claim a region in the pregame. if (game.currentPlayer().claim(data)) { // Increment to next player. game.currentPlayer().endTurn(); } } else if (!Region.allReinforcementsPlaced()) { if (game.currentPlayer().reinforce(data)) { game.currentPlayer().endTurn(); } } if (Region.allReinforcementsPlaced()) { game.incrementState(); } } else { if (await game.currentPlayer().act(data)) { game.currentPlayer().endTurn(); } else { const event = new CustomEvent("turnProgress"); document.dispatchEvent(event); } } } }); document.addEventListener("gameStateUpdate", async () => { if (game.isPregame()) { let firstPlayerIndex = await random.get( Object.keys(game.players).length, "first-player" ); let firstPlayer = Object.values(game.players).sort((a, b) => a.id < b.id ? -1 : 1 )[firstPlayerIndex]; firstPlayer.isPlaying = true; await barrier.wait(); const event = new CustomEvent("playerChange"); document.dispatchEvent(event); } });