Riskless/static/js/index.js

200 lines
5.2 KiB
JavaScript
Raw Normal View History

2023-01-29 16:47:37 +00:00
import { Player } from "./player.js";
2022-12-29 14:11:18 +00:00
const ID = window.crypto.randomUUID();
// Timeout to consider a player disconnected
const TIMEOUT = 30_000;
2022-12-29 14:11:18 +00:00
let players = {};
2023-01-29 16:47:37 +00:00
const WAITING = 0;
const PRE_GAME = 1;
const PLAYING = 2;
const POST_GAME = 3;
let game_state = WAITING;
document.addEventListener("DOMContentLoaded", () => {
2022-12-29 14:11:18 +00:00
let socket = io();
socket.on("connect", () => {
console.log("Connected!");
2023-01-29 16:47:37 +00:00
socket.emit("message", {
type: "ANNOUNCE",
id: window.crypto.randomUUID(),
author: ID,
name: "",
});
// Create self
players[ID] = new Player(ID, name);
2022-12-29 14:11:18 +00:00
});
socket.on("message", (data) => {
// Ignore any messages that originate from us.
2023-01-29 16:47:37 +00:00
if (data.author === ID) {
return;
}
switch (data.type) {
case "ANNOUNCE":
playerConnected(socket, data);
break;
case "KEEPALIVE":
keepAlive(data);
break;
case "SYNC":
sync(data);
break;
case "RANDOM":
cooperativeRandom(data);
break;
}
});
// Emit keepalive messages to inform other players we are still here
window.setInterval(() => {
2023-01-29 16:47:37 +00:00
socket.emit("message", {
type: "KEEPALIVE",
id: window.crypto.randomUUID(),
author: ID,
});
}, TIMEOUT / 5);
2022-12-29 14:11:18 +00:00
});
2023-01-29 16:47:37 +00:00
/**
* Process player connect packets: these inform that a new player has joined.
*
* @param socket Socket instance used to announce ourselves to new player
* @param data Packet received
*/
function playerConnected(socket, data) {
2023-01-29 16:47:37 +00:00
// Block players from joining mid-game
if (game_state !== WAITING) {
return;
}
// When a new player is seen, all announce to ensure they know all players.
if (players[data.author] === undefined) {
2023-01-29 16:47:37 +00:00
players[data.author] = new Player(data.author, data.name);
socket.emit("message", {
type: "ANNOUNCE",
id: window.crypto.randomUUID(),
author: ID,
name: "",
});
players[data.author].resetTimeout();
} else {
}
updatePlayerDom();
}
2023-01-29 16:47:37 +00:00
/**
* Process keep-alive packets: these are packets that check players are still online.
*
* @param data Packet received
*/
function keepAlive(data) {
2023-01-29 16:47:37 +00:00
players[data.author].resetTimeout();
}
/**
* Process sync packets: update player details like status and name.
*
* @param data Packet received
*/
function sync(data) {
players[data.author].name = data.name;
players[data.author].ready = data.ready;
if (allPlayersReady()) {
game_state = PRE_GAME;
// Decide turn order. "Master" begins a cooperative rng process.
//
}
}
function allPlayersReady() {
for (let player of Object.values(players)) {
if (!player.ready) {
return false;
}
}
return true;
}
2023-01-29 16:47:37 +00:00
// Track ongoing random sessions.
const randomSessions = {};
/**
* Process cooperative random protocol.
*
* @param socket Socket instance for communication
* @param data Packet received
*/
function cooperativeRandom(socket, data) {
// Step 0: extract relevant information from data
let session = randomSessions[data.session];
const stage = data.stage;
if (session === undefined) {
// Step 1: generate and encrypt our random value. 4 bytes = 32 bit integer
const noise = CryptoJS.libs.WordArray.random(4);
const key = CryptoJS.libs.WordArray.random(256);
const cipherText = CryptoJS.AES.encrypt(noise, key);
randomSessions[data.session] = {
range: data.range,
cipherTexts: {},
cipherKeys: {},
ourKey: key,
ourNoise: noise,
finalValue: -1,
};
session = randomSessions[data.session];
// Step 2: send our random value and wait for all responses
socket.emit("message", {
type: "RANDOM",
author: ID,
stage: "CIPHERTEXT",
range: data.range,
cipherText: cipherText,
});
}
if (stage === "CIPHERTEXT") {
session.cipherTexts[data.author] = data.cipherText;
if (Object.keys(session.cipherTexts).length === Object.keys(players).length) {
// Step 3: release our key once all players have sent a ciphertext
socket.emit("message", {
type: "RANDOM",
author: ID,
stage: "DECRYPT",
cipherKey: session.ourKey,
});
}
} else if (stage === "DECRYPT") {
session.cipherKeys[data.author] = data.cipherKey;
// Step 4: get final random value
if (Object.keys(session.cipherKeys).length === Object.keys(players).length) {
let total = 0;
for (let participant of Object.keys(session.cipherKeys)) {
total += CryptoJS.AES.decrypt(
session.cipherTexts[participant],
session.cipherKeys[participant]
);
}
session.finalValue = total % session.range;
}
}
}