diff options
-rw-r--r-- | package.json | 6 | ||||
-rw-r--r-- | public/common/columbia.css (renamed from public/common/battle_abc.css) | 18 | ||||
-rw-r--r-- | public/common/play.css (renamed from public/common/grid.css) | 9 | ||||
-rw-r--r-- | public/common/play.js (renamed from public/common/client.js) | 245 | ||||
-rw-r--r-- | public/images/sherlock-holmes-mirror.svg | 1 | ||||
-rw-r--r-- | server.js | 205 |
6 files changed, 280 insertions, 204 deletions
diff --git a/package.json b/package.json index 2cb73c2..2258084 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,10 @@ "express": "^4.17.1", "nodemailer": "^6.7.0", "pug": "^3.0.2", - "socket.io": "^4.3.1" + "ws": "^8.4.0" + }, + "optionalDependencies": { + "bufferutil": "^4.0.6", + "utf-8-validate": "^5.0.8" } } diff --git a/public/common/battle_abc.css b/public/common/columbia.css index 3a211a1..1a646b6 100644 --- a/public/common/battle_abc.css +++ b/public/common/columbia.css @@ -1,4 +1,4 @@ -/* ABC BATTLE DISPLAY FOR COLUMBIA BLOCK GAMES */ +/* BATTLE DIALOG FOR COLUMBIA BLOCK GAMES */ #battle { position: absolute; @@ -11,18 +11,22 @@ box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.5); visibility: hidden; } + #battle.show { visibility: visible; } + #battle th { padding: 0; border: 1px solid black; } + #battle td { padding: 0; border: 1px solid black; vertical-align: top; } + #battle_header { font-weight: bold; height: 2em; @@ -31,12 +35,14 @@ user-select: none; cursor: move; } + #battle_message { font-weight: normal; height: 2em; line-height: 2em; padding: 0 8px; } + #FA, #FB, #FC, #FD, #FF, #FR, #EA, #EB, #EC, #ED, #EF, #ER { display: flex; flex-wrap: wrap; @@ -64,11 +70,6 @@ display: none; } -.battle_menu .action:hover { background-color: red; } -.battle_menu .action.retreat:hover { background-color: #eee; } -.battle_menu .action.withdraw:hover { background-color: #eee; } -.battle_menu .action.pass:hover { background-color: gray; } - .battle_menu_list { margin-top: 5px; min-height: 26px; @@ -82,6 +83,11 @@ display: none; } +.battle_menu .action:hover { background-color: red; } +.battle_menu .action.retreat:hover { background-color: #eee; } +.battle_menu .action.withdraw:hover { background-color: #eee; } +.battle_menu .action.pass:hover { background-color: gray; } + .battle_menu.fire .action.fire { display: inline; } .battle_menu.retreat .action.retreat { display: inline; } .battle_menu.pass .action.pass { display: inline; } diff --git a/public/common/grid.css b/public/common/play.css index 228c0aa..64e9202 100644 --- a/public/common/grid.css +++ b/public/common/play.css @@ -25,7 +25,7 @@ body:not(.shift) .debug { display: none; } -body.Observer .resign, body.replay .resign { +body.Observer .resign { display: none; } @@ -184,7 +184,7 @@ header .replay { display: flex; } -header button.replay_button { +header .replay button { margin: 0; } @@ -311,12 +311,11 @@ header button.replay_button { border: 1px solid black; background-color: white; box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.5); - display: none; - grid-template-rows: auto minmax(0, 1fr) auto; + visibility: hidden; } #chat_window.show { - display: grid; + visibility: visible; } #chat_header { diff --git a/public/common/client.js b/public/common/play.js index d9a1f48..ede3a5b 100644 --- a/public/common/client.js +++ b/public/common/play.js @@ -117,13 +117,10 @@ window.addEventListener("focus", stop_blinker); function init_chat() { // only fetch new messages when we reconnect! if (chat !== null) { - console.log("RECONNECT CHAT"); - socket.emit("getchat", chat.log); + send_message("getchat", chat.log); return; } - console.log("CONNECT CHAT"); - let chat_window = document.createElement("div"); chat_window.id = "chat_window"; chat_window.innerHTML = ` @@ -156,7 +153,7 @@ function init_chat() { let input = document.getElementById("chat_input"); e.preventDefault(); if (input.value) { - socket.emit("chat", input.value); + send_message("chat", input.value); input.value = ""; } else { hide_chat(); @@ -179,7 +176,7 @@ function init_chat() { } }); - socket.emit("getchat", 0); + send_message("getchat", 0); } function save_chat() { @@ -251,6 +248,38 @@ function toggle_chat() { show_chat(); } +/* REMATCH BUTTON */ + +function remove_resign_menu() { + document.querySelectorAll(".resign").forEach(x => x.remove()); +} + +function goto_rematch() { + window.location = "/rematch/" + params.game_id + "/" + params.role; +} + +function goto_replay() { + window.location = "/" + params.title_id + "/replay:" + params.game_id; +} + +function on_game_over() { + function icon_button(id, img, title, fn) { + if (!document.getElementById(id)) { + let button = document.createElement("div"); + button.id = id; + button.title = title; + button.className = "icon_button"; + button.innerHTML = '<img src="/images/' + img + '.svg">'; + button.addEventListener("click", fn); + document.querySelector("header").appendChild(button); + } + } + icon_button("replay_button", "sherlock-holmes-mirror", "Watch replay", goto_replay); + if (player !== "Observer") + icon_button("rematch_button", "cycle", "Propose a rematch!", goto_rematch); + remove_resign_menu(); +} + /* CONNECT TO GAME SERVER */ function init_player_names(players) { @@ -261,76 +290,102 @@ function init_player_names(players) { } } -function init_play_client() { - const ROLE_SEL = [ - ".role.one", - ".role.two", - ".role.three", - ".role.four", - ".role.five", - ".role.six", - ".role.seven", - ]; +function send_message(cmd, arg) { + let data = JSON.stringify([cmd, arg]); + console.log("SEND %s %s", cmd, arg); + socket.send(data); +} - console.log("JOINING", params.title_id + "/" + params.game_id + "/" + params.role); +let reconnect_count = 0; +let reconnect_max = 10; - socket = io({ - transports: ["websocket"], - query: { title: params.title_id, game: params.game_id, role: params.role }, - }); +function connect_play() { + if (reconnect_count >= reconnect_max) { + document.title = "DISCONNECTED"; + document.getElementById("prompt").textContent = "Disconnected."; + return; + } - socket.on("connect", () => { - console.log("CONNECTED"); - document.querySelector("header").classList.remove("disconnected"); - }); + let protocol = (window.location.protocol === "http:") ? "ws" : "wss"; + let seen = document.getElementById("log").children.length; + let url = `${protocol}://${window.location.host}/play-socket?title=${params.title_id}&game=${params.game_id}&role=${params.role}&seen=${seen}`; - socket.on("disconnect", () => { - console.log("DISCONNECTED"); - document.getElementById("prompt").textContent = "Disconnected from server!"; - document.querySelector("header").classList.add("disconnected"); - }); + console.log("CONNECTING", url); + document.getElementById("prompt").textContent = "Connecting... "; + + socket = new WebSocket(url); - socket.on("roles", (me, players) => { - console.log("PLAYERS", me, JSON.stringify(players)); - player = me; - document.querySelector("body").classList.add(player.replace(/ /g, "_")); - if (player !== "Observer") - init_chat(); - init_player_names(players); + window.addEventListener('beforeunload', function () { + socket.close(1000); }); - socket.on("presence", (presence) => { - console.log("PRESENCE", JSON.stringify(presence)); - for (let i = 0; i < roles.length; ++i) { - let elt = document.getElementById(roles[i].id); - if (roles[i].role in presence) - elt.classList.add("present"); - else - elt.classList.remove("present"); + socket.onopen = function (evt) { + console.log("OPEN"); + document.querySelector("header").classList.remove("disconnected"); + reconnect_count = 0; + } + + socket.onclose = function (evt) { + console.log("CLOSE %d", evt.code); + if (evt.code === 1000 && evt.reason !== "") { + document.getElementById("prompt").textContent = "Disconnected: " + evt.reason; + document.title = "DISCONNECTED"; } - }); + if (evt.code !== 1000) { + document.querySelector("header").classList.add("disconnected"); + document.getElementById("prompt").textContent = `Reconnecting soon... (${reconnect_count+1}/${reconnect_max})`; + let wait = 1000 * (Math.random() + 0.5) * Math.pow(2, reconnect_count++); + console.log("WAITING %.1f TO RECONNECT", wait/1000); + setTimeout(connect_play, wait); + } + } - socket.on("state", (new_view) => { - console.log("STATE"); - view = new_view; - on_update_header(); - on_update(); - on_update_log(); - }); + socket.onmessage = function (evt) { + let [ cmd, arg ] = JSON.parse(evt.data); + console.log("MESSAGE %s", cmd); + switch (cmd) { + case 'error': + document.getElementById("prompt").textContent = arg; + break; - socket.on("save", (msg) => { - console.log("SAVE"); - window.localStorage[params.title_id + "/save"] = msg; - }); + case 'chat': + update_chat(arg[0], arg[1], arg[2], arg[3]); + break; - socket.on("error", (msg) => { - console.log("ERROR", msg); - document.getElementById("prompt").textContent = msg; - }); + case 'players': + player = arg[0]; + document.querySelector("body").classList.add(player.replace(/ /g, "_")); + if (player !== "Observer") + init_chat(); + else + remove_resign_menu(); + init_player_names(arg[1]); + break; - socket.on("chat", function (item) { - update_chat(item[0], item[1], item[2], item[3]); - }); + case 'presence': + for (let i = 0; i < roles.length; ++i) { + let elt = document.getElementById(roles[i].id); + if (roles[i].role in arg) + elt.classList.add("present"); + else + elt.classList.remove("present"); + } + break; + + case 'state': + view = arg; + on_update_header(); + on_update(); + on_update_log(); + if (view.game_over) + on_game_over(); + break; + + case 'save': + window.localStorage[params.title_id + "/save"] = msg; + break; + } + } } /* HEADER */ @@ -474,17 +529,16 @@ function send_action(verb, noun) { return; // Reset action list here so we don't send more than one action per server prompt! if (noun !== undefined) { - if (view.actions && view.actions[verb] && view.actions[verb].includes(noun)) { + let realnoun = Array.isArray(noun) ? noun[0] : noun; + if (view.actions && view.actions[verb] && view.actions[verb].includes(realnoun)) { view.actions = null; - console.log("ACTION", verb, JSON.stringify(noun)); - socket.emit("action", verb, noun); + send_message("action", [verb, noun]); return true; } } else { if (view.actions && view.actions[verb]) { view.actions = null; - console.log("ACTION", verb); - socket.emit("action", verb); + send_message("action", [verb]); return true; } } @@ -493,21 +547,21 @@ function send_action(verb, noun) { function confirm_resign() { if (window.confirm("Are you sure that you want to resign?")) - socket.emit("resign"); + send_message("resign"); } /* DEBUGGING */ function send_save() { - socket.emit("save"); + send_message("save"); } function send_restore() { - socket.emit("restore", window.localStorage[params.title_id + "/save"]); + send_message("restore", window.localStorage[params.title_id + "/save"]); } function send_restart(scenario) { - socket.emit("restart", scenario); + send_message("restart", scenario); } /* REPLAY */ @@ -546,9 +600,10 @@ async function require(path) { let replay = null; -async function init_replay_client() { +async function init_replay() { + remove_resign_menu(); + document.getElementById("prompt").textContent = "Loading replay..."; - document.querySelector("body").classList.add("replay"); console.log("LOADING RULES"); let rules = await require("rules.js"); @@ -660,7 +715,7 @@ async function init_replay_client() { if (viewpoint === "Active") { player = s.active; - if (player === "All" || player === "Both" || !player) + if (player === "All" || player === "Both" || player === "None" || !player) player = "Observer"; } @@ -679,10 +734,13 @@ async function init_replay_client() { view.game_over = 1; if (replay.length > 0) { - if (document.querySelector("body").classList.contains("shift")) - view.prompt = `[${p}/${replay.length}] ${s.active} / ${s.state} / ${replay[p].action} ${replay[p].arguments}`; - else + if (document.querySelector("body").classList.contains("shift")) { + view.prompt = `[${p}/${replay.length}] ${s.active} / ${s.state}`; + if (p < replay.length) + view.prompt += ` / ${replay[p].action} ${replay[p].arguments}`; + } else { view.prompt = "[" + p + "/" + replay.length + "] " + view.prompt; + } } if (log_length < view.log.length) view.log_start = log_length; @@ -696,11 +754,10 @@ async function init_replay_client() { on_update_log(); } - function replay_button(div, label, fn) { + function text_button(div, txt, fn) { let button = document.createElement("button"); button.addEventListener("click", fn); - button.className = "replay_button"; - button.textContent = label; + button.textContent = txt; div.appendChild(button); return button; } @@ -715,20 +772,20 @@ async function init_replay_client() { let div = document.createElement("div"); div.className = "replay"; - replay_button(div, "Active", () => set_viewpoint("Active")); + text_button(div, "Active", () => set_viewpoint("Active")); for (let r of roles) - replay_button(div, r.role, () => set_viewpoint(r.role)); - replay_button(div, "Observer", () => set_viewpoint("Observer")); + text_button(div, r.role, () => set_viewpoint(r.role)); + text_button(div, "Observer", () => set_viewpoint("Observer")); document.querySelector("header").appendChild(div); div = document.createElement("div"); div.className = "replay"; - replay_button(div, "<<<", () => goto_replay(1)); - replay_button(div, "<<", () => goto_replay(prev())); - replay_button(div, "<\xa0", () => goto_replay(p-1)); - replay_button(div, "\xa0>", () => goto_replay(p+1)); - replay_button(div, ">>", () => goto_replay(next())); - replay_button(div, "Run", play_pause_replay).style.width = "65px"; + text_button(div, "<<<", () => goto_replay(1)); + text_button(div, "<<", () => goto_replay(prev())); + text_button(div, "<\xa0", () => goto_replay(p-1)); + text_button(div, "\xa0>", () => goto_replay(p+1)); + text_button(div, ">>", () => goto_replay(next())); + text_button(div, "Run", play_pause_replay).style.width = "65px"; document.querySelector("header").appendChild(div); if (window.location.hash === "") @@ -744,7 +801,7 @@ async function init_replay_client() { } } -if (params.mode === "play") - init_play_client(); if (params.mode === "replay") - init_replay_client(); + init_replay(); +if (params.mode === "play") + connect_play(); diff --git a/public/images/sherlock-holmes-mirror.svg b/public/images/sherlock-holmes-mirror.svg new file mode 100644 index 0000000..f2f0f19 --- /dev/null +++ b/public/images/sherlock-holmes-mirror.svg @@ -0,0 +1 @@ +<svg style="height:512px;width:512px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M326.582 36.882s24.67 5.747 35.756 15.557c38.586 34.149 51.256 67.652 53.746 105.53l43.97 47.023c-96.221-17.906-207.672-21.92-340.581-20.912 21.002-14.144 41.37-25.753 59.322-36.814 14.473-35.496 33.701-76.597 65.934-95.373 14.145-8.24 29.144-14.024 43.962-16.83 10.736-20.307 35.4-11.819 37.89 1.82zM407.023 214.8c-20.328 40.62-56.635 79.575-89.761 103.012-18.256 12.63-36.742 21.653-51.035 24.144-11.946 2.242-23.46-1.416-28.825-10.672l-.002-.004v-.002c-8.103-14.299-14.714-28.724-20.359-43.197-15.776 1.737-33.408 2.541-38.04-1.178-7.31-5.871 8.751-56.088 16.056-69.443C192 211.24 192 204.665 192 199.88c39.436-2.194 150.638 6.423 215.023 14.92zm-342.142 6.07c13.335.302 24.897 9.857 33.428 22.668 9.413 14.137 16.138 33.34 18.798 55.055 2.66 21.714.771 41.971-4.95 57.963-4.29 11.99-11.133 22.318-20.655 27.515l10.77 108.676-17.913 1.775-10.615-107.13c-12.015-1.592-22.443-10.62-30.299-22.418-9.413-14.137-16.136-33.339-18.797-55.053-2.66-21.714-.77-41.974 4.952-57.965 5.721-15.991 15.983-29.026 31.087-30.877a29.036 29.036 0 0 1 4.194-.209zm-2.004 18.076c-5.494.673-11.846 6.541-16.33 19.075-4.485 12.533-6.397 30.421-4.033 49.71 2.363 19.29 8.538 36.186 15.916 47.266 7.377 11.08 14.955 15.242 20.449 14.568 5.494-.673 11.844-6.54 16.328-19.074 4.485-12.533 6.396-30.42 4.033-49.709-2.363-19.289-8.536-36.187-15.914-47.267-5.011-6.164-12.69-15.168-20.449-14.569zm349.951 3.584c6.243 8.543 13.975 17.27 23.111 25.744 12.11 11.231 26.664 21.827 43.198 30.211-37.101 7.524-77.514 23.385-115.21 42.594-42.326 21.57-80.776 47.18-106.775 71.145l-2.314-24.686c11.388-12.634 23.526-23.83 35.869-34.252 12.152-5.001 24.557-12.203 36.797-20.672 36.371-25.164 70.878-60.916 85.324-90.084zM68.246 253.806c-.036 21.503-3.015 45.534-9.771 64.632-6.729-19.745-7.02-55.246 9.771-64.632zm137.598 54.43a353.014 353.014 0 0 0 7.74 16.574c-7.33 5.423-11.536 10.592-13.904 15.505-3.119 6.47-3.49 13.077-2.176 21.295 2.24 14.002 10.213 31.472 14.32 52.23 9.049 12.67 1.565 56.042-18.265 60.938-44.677 11.03-69.71-35.7-59.614-46.716 9.817-10.713 37.598-19.736 57.92-19.952-4.048-14.733-9.79-28.985-12.136-43.656-1.666-10.412-1.325-21.456 3.736-31.955 4.255-8.827 11.576-16.856 22.379-24.264zm272.875 8.81c4.751 7.296 9.937 15.484 15.281 24.413V494H238.848c2.921-14.12 5.609-28.7 8.763-42.629.65-5.674 5.269-9.093 9.059-13.314 21.957-24.459 66.328-55.92 115.432-80.942 35.16-17.917 72.94-32.625 106.617-40.07z"/></svg>
\ No newline at end of file @@ -4,8 +4,9 @@ const fs = require('fs'); const crypto = require('crypto'); const http = require('http'); const https = require('https'); -const socket_io = require('socket.io'); +const { WebSocketServer } = require('ws'); const express = require('express'); +const url = require('url'); const compression = require('compression'); const sqlite3 = require('better-sqlite3'); @@ -100,10 +101,10 @@ app.locals.SITE_NAME = SITE_NAME; let http_port = process.env.HTTP_PORT || 8080; let http_server = http.createServer(app); -let http_io = socket_io(http_server); +let http_wss = new WebSocketServer({server: http_server}); http_server.keepAliveTimeout = 0; http_server.listen(http_port, '0.0.0.0', () => console.log('listening HTTP on *:' + http_port)); -let io = http_io; +let wss = http_wss; let https_port = process.env.HTTPS_PORT; if (https_port) { @@ -111,13 +112,10 @@ if (https_port) { key: fs.readFileSync(process.env.SSL_KEY || "key.pem"), cert: fs.readFileSync(process.env.SSL_CERT || "cert.pem") }, app); - let https_io = socket_io(https_server); + let https_wss = new WebSocketServer({server: https_server}); https_server.listen(https_port, '0.0.0.0', () => console.log('listening HTTPS on *:' + https_port)); - http_server.keepAliveTimeout=0; - io = { - use: function (fn) { http_io.use(fn); https_io.use(fn); }, - on: function (ev,fn) { http_io.on(ev,fn); https_io.on(ev,fn); }, - }; + https_server.keepAliveTimeout = 0; + wss = { on: function (ev,fn) { http_wss.on(ev,fn); https_wss.on(ev,fn); } }; } /* @@ -129,19 +127,16 @@ function random_seed() { } function LOG(req, ...msg) { - let name; - if (req.user) - name = `"${req.user.name}" <${req.user.mail}>`; - else - name = "guest"; let time = new Date().toISOString().substring(0,19).replace("T", " "); + let name = req.user ? `"${req.user.name}" <${req.user.mail}>` : "guest"; console.log(time, req.connection.remoteAddress, name, ...msg); } function SLOG(socket, ...msg) { let time = new Date().toISOString().substring(0,19).replace("T", " "); - console.log(time, socket.request.connection.remoteAddress, socket.user_name, - socket.title_id + "/" + socket.game_id + "/" + socket.role, ...msg); + let name = socket.user ? `"${socket.user.name}" <${socket.user.mail}>` : "guest"; + console.log(time, socket.ip, name, + "WS /" + socket.title_id + "/" + socket.game_id + "/" + socket.role, ...msg); } function human_date(time) { @@ -287,19 +282,6 @@ app.use(function (req, res, next) { return next(); }); -io.use(function (socket, next) { - let sid = login_cookie(socket.request); - if (sid) - socket.user_id = login_sql_select.get(sid); - else - socket.user_id = 0; - if (socket.user_id) - socket.user_name = SQL_SELECT_USER_NAME.get(socket.user_id); - else - socket.user_name = "guest"; - return next(); -}); - function must_be_logged_in(req, res, next) { if (!req.user) return res.redirect('/login?redirect=' + encodeURIComponent(req.originalUrl)); @@ -1378,7 +1360,7 @@ app.get('/:title_id/play\::game_id\::role', must_be_logged_in, function (req, re let game_id = req.params.game_id; let role = req.params.role; if (!SQL_AUTHORIZE_GAME_ROLE.get(title_id, game_id, role, user_id)) - return res.send("You are not assigned that role."); + return res.status(404).send("Invalid game ID."); return res.sendFile(__dirname + '/public/' + title_id + '/play.html'); }); @@ -1515,7 +1497,7 @@ function mail_ready_to_start_notification(user, game_id, interval) { function mail_your_turn_notification_to_offline_users(game_id, old_active, active) { function is_online(game_id, user_id) { for (let other of clients[game_id]) - if (other.user_id === user_id) + if (other.user && other.user.user_id === user_id) return true; return false; } @@ -1572,21 +1554,25 @@ setInterval(notify_ready_to_start_reminder, 5 * 60 * 1000); let clients = {}; +function send_message(socket, cmd, arg) { + socket.send(JSON.stringify([cmd, arg])); +} + function send_state(socket, state) { try { let view = socket.rules.view(state, socket.role); - if (socket.log_length < view.log.length) - view.log_start = socket.log_length; + if (socket.seen < view.log.length) + view.log_start = socket.seen; else view.log_start = view.log.length; - socket.log_length = view.log.length; + socket.seen = view.log.length; view.log = view.log.slice(view.log_start); if (state.state === 'game_over') view.game_over = 1; - socket.emit('state', view); + send_message(socket, 'state', view); } catch (err) { console.log(err); - return socket.emit('error', err.toString()); + return send_message(socket, 'error', err.toString()); } } @@ -1624,7 +1610,7 @@ function on_action(socket, action, arg) { put_replay(socket.game_id, socket.role, action, arg); } catch (err) { console.log(err); - return socket.emit('error', err.toString()); + return send_message(socket, 'error', err.toString()); } } @@ -1638,7 +1624,7 @@ function on_resign(socket) { put_replay(socket.game_id, socket.role, 'resign', null); } catch (err) { console.log(err); - return socket.emit('error', err.toString()); + return send_message(socket, 'error', err.toString()); } } @@ -1648,25 +1634,25 @@ function on_getchat(socket, seen) { if (chat.length > 0) SLOG(socket, "GETCHAT", seen, chat.length); for (let i = 0; i < chat.length; ++i) - socket.emit('chat', chat[i]); + send_message(socket, 'chat', chat[i]); } catch (err) { console.log(err); - return socket.emit('error', err.toString()); + return send_message(socket, 'error', err.toString()); } } function on_chat(socket, message) { message = message.substring(0,4000); try { - let chat = SQL_INSERT_GAME_CHAT.get(socket.game_id, socket.user_id, message); - chat[2] = socket.user_name; + let chat = SQL_INSERT_GAME_CHAT.get(socket.game_id, socket.user.user_id, message); + chat[2] = socket.user.name; SLOG(socket, "CHAT"); for (let other of clients[socket.game_id]) if (other.role !== "Observer") - other.emit('chat', chat); + send_message(other, 'chat', chat); } catch (err) { console.log(err); - return socket.emit('error', err.toString()); + return send_message(socket, 'error', err.toString()); } } @@ -1675,11 +1661,11 @@ function on_debug(socket) { try { let game_state = SQL_SELECT_GAME_STATE.get(socket.game_id); if (!game_state) - return socket.emit('error', "No game with that ID."); - socket.emit('debug', game_state); + return send_message(socket, 'error', "No game with that ID."); + send_message(socket, 'debug', game_state); } catch (err) { console.log(err); - return socket.emit('error', err.toString()); + return send_message(socket, 'error', err.toString()); } } @@ -1688,16 +1674,16 @@ function on_save(socket) { try { let game_state = SQL_SELECT_GAME_STATE.get(socket.game_id); if (!game_state) - return socket.emit('error', "No game with that ID."); - socket.emit('save', game_state); + return send_message(socket, 'error', "No game with that ID."); + send_message(socket, 'save', game_state); } catch (err) { console.log(err); - return socket.emit('error', err.toString()); + return send_message(socket, 'error', err.toString()); } } function on_restore(socket, state_text) { - SLOG(socket, 'RESTORE'); + SLOG(socket, "RESTORE"); try { let state = JSON.parse(state_text); state.seed = random_seed(); // reseed! @@ -1709,7 +1695,7 @@ function on_restore(socket, state_text) { send_state(other, state); } catch (err) { console.log(err); - return socket.emit('error', err.toString()); + return send_message(socket, 'error', err.toString()); } } @@ -1718,89 +1704,112 @@ function broadcast_presence(game_id) { for (let socket of clients[game_id]) presence[socket.role] = true; for (let socket of clients[game_id]) - socket.emit('presence', presence); + send_message(socket, 'presence', presence); +} + +function on_restart(socket, scenario) { + try { + let seed = random_seed(); + let state = socket.rules.setup(seed, scenario, {}, socket.players); + put_replay(socket.game_id, null, 'setup', [seed, scenario, null, socket.players]); + for (let other of clients[socket.game_id]) { + other.seen = 0; + send_state(other, state); + } + let state_text = JSON.stringify(state); + SQL_UPDATE_GAME_RESULT.run(1, null, socket.game_id); + SQL_UPDATE_GAME_STATE.run(socket.game_id, state_text, state.active); + } catch (err) { + console.log(err); + return send_message(socket, 'error', err.toString()); + } } -io.on('connection', (socket) => { - socket.title_id = socket.handshake.query.title || "unknown"; - socket.game_id = socket.handshake.query.game | 0; - socket.role = socket.handshake.query.role; - socket.log_length = 0; +function handle_message(socket, cmd, arg) { + switch (cmd) { + case 'action': on_action(socket, arg[0], arg[1]); break; + case 'resign': on_resign(socket); break; + case 'getchat': on_getchat(socket, arg); break; + case 'chat': on_chat(socket, arg); break; + case 'debug': on_debug(socket); break; + case 'save': on_save(socket); break; + case 'restore': on_restore(socket, arg); break; + case 'restart': on_restart(socket, arg); break; + } +} + +wss.on('connection', (socket, req, client) => { + let u = url.parse(req.url, true); + if (u.pathname !== '/play-socket') + return setTimeout(() => socket.close(1000, "Invalid request."), 30000); + req.query = u.query; + + let user_id = 0; + let sid = login_cookie(req); + if (sid) + user_id = login_sql_select.get(sid); + if (user_id) + socket.user = SQL_SELECT_USER_INFO.get(user_id); + + socket.ip = req.connection.remoteAddress; + socket.title_id = req.query.title || "unknown"; + socket.game_id = req.query.game | 0; + socket.role = req.query.role; + socket.seen = req.query.seen | 0; socket.rules = RULES[socket.title_id]; - SLOG(socket, "CONNECT"); + SLOG(socket, "OPEN " + socket.seen); try { let title_id = SQL_SELECT_GAME_TITLE.get(socket.game_id); if (title_id !== socket.title_id) - return socket.emit('error', "Invalid game ID."); + return socket.close(1000, "Invalid game ID."); - let players = SQL_SELECT_PLAYERS_JOIN.all(socket.game_id); + let players = socket.players = SQL_SELECT_PLAYERS_JOIN.all(socket.game_id); if (socket.role !== "Observer") { - if (!socket.user_id) - return socket.emit('error', "You are not logged in!"); + if (!socket.user) + return socket.close(1000, "You are not logged in!"); if (socket.role && socket.role !== 'undefined' && socket.role !== 'null') { - let me = players.find(p => p.user_id === socket.user_id && p.role === socket.role); - if (!me) { - socket.role = "Observer"; - return socket.emit('error', "You aren't assigned that role!"); - } + let me = players.find(p => p.user_id === socket.user.user_id && p.role === socket.role); + if (!me) + return socket.close(1000, "You aren't assigned that role!"); } else { - let me = players.find(p => p.user_id === socket.user_id); + let me = players.find(p => p.user_id === socket.user.user_id); socket.role = me ? me.role : "Observer"; } } - socket.emit('roles', socket.role, players); + if (socket.seen === 0) + send_message(socket, 'players', [socket.role, players]); if (clients[socket.game_id]) clients[socket.game_id].push(socket); else clients[socket.game_id] = [ socket ]; - socket.on('disconnect', () => { - SLOG(socket, "DISCONNECT"); + socket.on('close', (code, reason) => { + SLOG(socket, "CLOSE " + code); clients[socket.game_id].splice(clients[socket.game_id].indexOf(socket), 1); - if (socket.role !== "Observer") - broadcast_presence(socket.game_id); + broadcast_presence(socket.game_id); }); if (socket.role !== "Observer") { - socket.on('action', (action, arg) => on_action(socket, action, arg)); - socket.on('resign', () => on_resign(socket)); - socket.on('getchat', (seen) => on_getchat(socket, seen)); - socket.on('chat', (message) => on_chat(socket, message)); - - socket.on('debug', () => on_debug(socket)); - socket.on('save', () => on_save(socket)); - socket.on('restore', (state) => on_restore(socket, state)); - socket.on('restart', (scenario) => { + socket.on('message', (data) => { try { - let seed = random_seed(); - let state = socket.rules.setup(seed, scenario, {}, players); - put_replay(socket.game_id, null, 'setup', [seed, scenario, null, players]); - for (let other of clients[socket.game_id]) { - other.log_length = 0; - send_state(other, state); - } - let state_text = JSON.stringify(state); - SQL_UPDATE_GAME_RESULT.run(1, null, socket.game_id); - SQL_UPDATE_GAME_STATE.run(socket.game_id, state_text, state.active); + let [ cmd, arg ] = JSON.parse(data); + handle_message(socket, cmd, arg); } catch (err) { - console.log(err); - return socket.emit('error', err.toString()); + send_message(socket, 'error', err); } }); } broadcast_presence(socket.game_id); - send_state(socket, get_game_state(socket.game_id)); - } catch (err) { console.log(err); - socket.emit('error', err.message); + socket.close(1000, err.message); } }); |