Compare commits

...

3 Commits

Author SHA1 Message Date
MaciejkaG
b787e79a00 Translation and animation improvements 2024-04-11 19:37:41 +02:00
MaciejkaG
ad23b8a18d Minor design/UI/UX improvements and fixes 2024-04-11 19:32:00 +02:00
MaciejkaG
fc01f72fa4 Finishing touches to the new login 2024-04-11 19:10:45 +02:00
11 changed files with 392 additions and 47 deletions

130
index.js
View File

@ -95,7 +95,10 @@ app.get('/privacy', (req, res) => {
app.get('/', async (req, res) => { app.get('/', async (req, res) => {
let login = loginState(req); let login = loginState(req);
if (login != 2) { if (login == 0) {
req.session.userAgent = req.get('user-agent');
req.session.loggedIn = 0;
const locale = new Lang(req.acceptsLanguages()); const locale = new Lang(req.acceptsLanguages());
res.render('landing', { res.render('landing', {
@ -104,6 +107,8 @@ app.get('/', async (req, res) => {
} }
}); });
// res.redirect('/login'); // res.redirect('/login');
} else if (login != 2) {
res.redirect("/login");
} else if (req.session.nickname == null) { } else if (req.session.nickname == null) {
auth.getLanguage(req.session.userId).then(language => { auth.getLanguage(req.session.userId).then(language => {
var locale; var locale;
@ -359,18 +364,111 @@ io.on('connection', async (socket) => {
const req = socket.request; const req = socket.request;
const session = req.session; const session = req.session;
socket.session = session; socket.session = session;
// if (session.loginState != 2) {
// socket.on('email login', (email, callback) => {
// callback(session.nickname);
// });
// }
if (!await GInfo.isPlayerInGame(socket)) { if (!session.loggedIn) {
if (session.nickname == null) { socket.on('email login', (email, callback) => {
socket.disconnect(); let login = socket.request.session.loggedIn;
if (login == 0 && email != null && validateEmail(email)) {
if (checkFlag('authless')) {
auth.loginAuthless(email).then(async result => {
req.session.reload((err) => {
if (err) return socket.disconnect();
req.session.userId = result.uid;
req.session.loggedIn = 2;
req.session.save();
});
callback({ status: "ok", next: "done" });
});
return; return;
} }
const locale = new Lang(session.langs);
auth.startVerification(email, getIPSocket(socket), socket.client.request.headers["user-agent"], locale.lang).then(async result => {
if (result.status === 1 || result.status === -1) {
req.session.reload((err) => {
if (err) return socket.disconnect();
req.session.userId = result.uid;
req.session.loggedIn = 1;
req.session.save();
});
callback({ status: "ok", next: "auth" });
} else {
callback({ status: "SrvErr", error: locale.t("landing.Server error") });
}
}).catch((err) => {
const locale = new Lang(session.langs);
callback({ success: false, error: locale.t("landing.Unknown error") });
throw err;
});
} else {
const locale = new Lang(session.langs);
auth.loginAuthless(email).then(async result => {
req.session.reload((err) => {
if (err) return socket.disconnect();
req.session.userId = result.uid;
req.session.loggedIn = 2;
req.session.save();
});
callback({ success: false, error: locale.t("landing.Wrong email address") });
});
}
});
socket.on('email auth', async (code, callback) => {
let login = socket.request.session.loggedIn;
if (login == 1 && code != null && code.length <= 10 && code.length >= 8) {
let finishResult = await auth.finishVerification(req.session.userId, code);
if (finishResult) {
req.session.reload((err) => {
if (err) return socket.disconnect();
req.session.loggedIn = 2;
req.session.save();
});
callback({ status: "ok", next: "done" });
} else {
const locale = new Lang(session.langs);
callback({ success: false, error: locale.t("landing.Wrong authorisation code") });
}
} else {
const locale = new Lang(session.langs);
callback({ success: false, error: locale.t("landing.Wrong authorisation code") });
}
});
socket.on('disconnecting', () => {
if (socket.request.session.loggedIn == 1) {
req.session.reload((err) => {
if (err) return socket.disconnect();
req.session.loggedIn = 0;
req.session.save();
});
}
});
}
if (!await GInfo.isPlayerInGame(socket) || session.nickname != null) {
// if (session.nickname == null) {
// socket.disconnect();
// return;
// }
socket.on('whats my nick', (callback) => { socket.on('whats my nick', (callback) => {
callback(session.nickname); callback(session.nickname);
}); });
@ -524,12 +622,16 @@ io.on('connection', async (socket) => {
} }
}); });
socket.on('logout', () => {
session.destroy();
});
socket.on('disconnecting', () => { socket.on('disconnecting', () => {
if (bships.isPlayerInRoom(socket)) { if (bships.isPlayerInRoom(socket)) {
io.to(socket.rooms[1]).emit("player left"); io.to(socket.rooms[1]).emit("player left");
} }
}); });
} else { } else if (session.nickname != null) {
const playerGame = await GInfo.getPlayerGameData(socket); const playerGame = await GInfo.getPlayerGameData(socket);
if (playerGame.data.state === 'pregame') { if (playerGame.data.state === 'pregame') {
@ -784,6 +886,14 @@ function getIP(req) {
} }
} }
function getIPSocket(socket) {
if (checkFlag("cloudflare_mode")) {
return socket.client.request.headers['cf-connecting-ip'];
} else {
return socket.client.request.headers['x-forwarded-for'] || socket.handshake.address;
}
}
function checkFlag(key) { function checkFlag(key) {
if (flags) { if (flags) {
return flags.includes(key); return flags.includes(key);

View File

@ -9,11 +9,23 @@
"Connection error": "Connection error" "Connection error": "Connection error"
}, },
"email": {
"This is your Statki authorisation code": "%s is your Statki authorisation code"
},
"landing": { "landing": {
"The #1 online multiplayer battleships game": "The #1 online multiplayer battleships game", "The #1 online multiplayer battleships game": "The #1 online multiplayer battleships game",
"SCROLL DOWN": "SCROLL DOWN", "SCROLL DOWN": "SCROLL DOWN",
"Privacy policy": "Privacy policy" "Privacy policy": "Privacy policy",
"Server error": "Server error",
"Unkown error": "Unknown error",
"Wrong email address": "Wrong e-mail address",
"Wrong authorisation code": "Wrong authorisation code",
"E-mail address is required": "E-mail address is required!",
"Auth code is required": "Auth code is required"
}, },
"menu": { "menu": {
@ -65,7 +77,8 @@
"No matches played": "No matches played" "No matches played": "No matches played"
}, },
"Settings": { "Settings": {
"General": "General" "General": "General",
"Log out": "Log out"
}, },
"General": { "General": {
"Unknown error occured": "Unknown error occured", "Unknown error occured": "Unknown error occured",

View File

@ -9,11 +9,23 @@
"Connection error": "Błąd połączenia" "Connection error": "Błąd połączenia"
}, },
"email": {
"This is your Statki authorisation code": "%s to twój kod autoryzacyjny do statków"
},
"landing": { "landing": {
"The #1 online multiplayer battleships game": "Najlepsza wielosobowa gra w statki", "The #1 online multiplayer battleships game": "Najlepsza wielosobowa gra w statki",
"SCROLL DOWN": "SCROLLUJ W DÓŁ", "SCROLL DOWN": "SCROLLUJ W DÓŁ",
"Privacy policy": "Polityka prywatności" "Privacy policy": "Polityka prywatności",
"Server error": "Bład serwera",
"Unkown error": "Nieznany błąd",
"Wrong email address": "Niepoprawny adres e-mail",
"Wrong authorisation code": "Niepoprawny kod autoryzacji",
"E-mail address is required": "Adres e-mail jest wymagany!",
"Auth code is required": "Kod autoryzacji jest wymagany!"
}, },
"menu": { "menu": {
@ -66,7 +78,8 @@
"No matches played": "Nie zagrano żadnych meczy" "No matches played": "Nie zagrano żadnych meczy"
}, },
"Settings": { "Settings": {
"General": "Ogólne" "General": "Ogólne",
"Log out": "Wyloguj się"
}, },
"General": { "General": {
"Unknown error occured": "Wystąpił nieznany błąd", "Unknown error occured": "Wystąpił nieznany błąd",

View File

@ -4,6 +4,8 @@
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
padding: 2rem;
box-sizing: border-box;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
text-align: center; text-align: center;
@ -11,6 +13,8 @@
align-items: center; align-items: center;
transform: translateY(-3rem); transform: translateY(-3rem);
font-size: 1.25em; font-size: 1.25em;
transition: opacity 0.3s;
} }
.landing p { .landing p {
@ -41,7 +45,7 @@
z-index: 1; z-index: 1;
transition: translate 1.5s ease-in-out; transition: translate 1s ease-in-out;
} }
.bgShip#bgs1 { .bgShip#bgs1 {
@ -114,12 +118,21 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 5; z-index: 5;
transition: all 0.5s 0.3s ease, top 0.6s ease-in-out; transition: all 0.5s 0.3s, transform 0.5s 0.2s, opacity 0.5s 0.2s, top 0.6s ease-in-out;
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
backdrop-filter: blur(0px); backdrop-filter: blur(0px);
} }
body.closed .loginContainer {
opacity: 0;
transform: scale(0.8);
}
body.closed .landing {
opacity: 0;
}
.loginContainer.active { .loginContainer.active {
top: 0; top: 0;
background-color: rgba(0, 0, 0, 0.6); background-color: rgba(0, 0, 0, 0.6);
@ -249,8 +262,10 @@
/* margin: 0; */ /* margin: 0; */
left: 50vw; left: 50vw;
transform: translateX(-50%); transform: translateX(-50%);
width: 50%;
z-index: 50; z-index: 50;
font-size: 16px; font-size: 16px;
text-align: center;
font-family: 'Roboto Mono', monospace; font-family: 'Roboto Mono', monospace;
} }
@ -263,3 +278,14 @@
.footer a:hover { .footer a:hover {
text-decoration: underline; text-decoration: underline;
} }
@media only screen and (max-width: 820px) {
.bgShip {
display: none;
}
.footer {
bottom: 15px;
width: 90%;
}
}

View File

@ -14,7 +14,7 @@
} }
.bshipstoast { .bshipstoast {
background-color: rgba(0, 0, 0, 0.6); background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(15px); backdrop-filter: blur(15px);
color: white; color: white;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
@ -338,3 +338,26 @@ nav span:hover {
outline: none; outline: none;
margin-left: 2rem; margin-left: 2rem;
} }
#settingsView a {
text-decoration: none;
color: var(--dynamic);
transition: opacity 0.3s;
}
#settingsView a:hover {
opacity: 0.6;
text-decoration: underline;
}
#logout {
color: var(--danger);
cursor: pointer;
transition: opacity 0.3s;
}
#logout:hover {
opacity: 0.6;
}

View File

@ -2,8 +2,12 @@ String.prototype.replaceAt = function (index, replacement) {
return this.substring(0, index) + replacement + this.substring(index + replacement.length); return this.substring(0, index) + replacement + this.substring(index + replacement.length);
} }
const socket = io();
const charset = ["0", "1", "!", "@", "#", "$", "%", "&"]; const charset = ["0", "1", "!", "@", "#", "$", "%", "&"];
const initialContent = $("#scrolldowntext").html();
setInterval(() => { setInterval(() => {
var content = $("#scrolldowntext").html(); var content = $("#scrolldowntext").html();
const len = content.length; const len = content.length;
@ -22,6 +26,11 @@ setInterval(() => {
setTimeout(() => { setTimeout(() => {
content = content.replaceAt(i, previousChar); content = content.replaceAt(i, previousChar);
$("#scrolldowntext").html(content); $("#scrolldowntext").html(content);
if (i == len - 1) {
content = initialContent;
$("#scrolldowntext").html(initialContent);
}
}, duration * len + duration * i); }, duration * len + duration * i);
}, duration * i); }, duration * i);
} }
@ -30,6 +39,30 @@ setInterval(() => {
document.addEventListener("wheel", (event) => { document.addEventListener("wheel", (event) => {
if (event.deltaY > 0) { if (event.deltaY > 0) {
$(".loginContainer").addClass("active"); $(".loginContainer").addClass("active");
animateShips();
} else if (event.deltaY < 0) {
$(".loginContainer").removeClass("active");
}
});
let touchStart = 0;
window.addEventListener("touchstart", function (event) {
touchStart = event.touches[0].clientY;
});
window.addEventListener("touchend", function (event) {
touchEnd = event.changedTouches[0].clientY;
if (touchStart - touchEnd > 50) {
$(".loginContainer").addClass("active");
animateShips();
} else if (touchStart - touchEnd < -50) {
$(".loginContainer").removeClass("active");
}
});
function animateShips() {
setTimeout(() => { setTimeout(() => {
for (let i = 1; i <= 10; i++) { for (let i = 1; i <= 10; i++) {
setTimeout(() => { setTimeout(() => {
@ -43,20 +76,122 @@ document.addEventListener("wheel", (event) => {
$(field).css("scale", "1"); $(field).css("scale", "1");
} }
}); });
} else if (event.deltaY < 0) {
$(".loginContainer").removeClass("active");
} }
});
switchView("loginView"); switchView("loginView");
const form = document.getElementById('loginForm'); const loginForm = document.getElementById('loginForm');
form.addEventListener('submit', (e) => { const emailInput = document.getElementById('email');
loginForm.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
console.log("a"); if (emailInput.value) {
lockUI(true);
console.log("Logging in with e-mail:", emailInput.value);
socket.emit("email login", emailInput.value, (response) => {
console.log(response);
switch (response.status) {
case "ok":
console.log("Logged in.");
console.log(response);
if (response.next === "done") {
console.log("No authorisation required.");
$("body").addClass("closed");
setTimeout(() => {
window.location.reload();
}, 700);
return;
}
lockUI(false);
switchView("authView"); switchView("authView");
progressParalax(); progressParalax();
break;
default:
Toastify({
text: response.error,
duration: 5000,
newWindow: true,
gravity: "bottom",
position: "right",
stopOnFocus: true,
className: "bshipstoast",
}).showToast();
lockUI(false);
break;
}
});
emailInput.value = '';
} else {
Toastify({
text: window.locale["E-mail address is required"],
duration: 5000,
newWindow: true,
gravity: "bottom",
position: "right",
stopOnFocus: true,
className: "bshipstoast",
}).showToast();
}
});
const authForm = document.getElementById('authForm');
const codeInput = document.getElementById('authcode');
authForm.addEventListener('submit', (e) => {
e.preventDefault();
if (codeInput.value) {
lockUI(true);
console.log("Logging in with e-mail:", codeInput.value);
socket.emit("email auth", codeInput.value, (response) => {
switch (response.status) {
case "ok":
console.log("Authorised.");
lockUI(false);
$("body").addClass("closed");
setTimeout(() => {
window.location.reload();
}, 700);
break;
default:
Toastify({
text: response.error,
duration: 5000,
newWindow: true,
gravity: "bottom",
position: "right",
stopOnFocus: true,
className: "bshipstoast",
}).showToast();
lockUI(false);
break;
}
});
emailInput.value = '';
} else {
Toastify({
text: window.locale["Auth code is required"],
duration: 5000,
newWindow: true,
gravity: "bottom",
position: "right",
stopOnFocus: true,
className: "bshipstoast",
}).showToast();
}
}); });
var parallaxTranslateX = 0; var parallaxTranslateX = 0;

View File

@ -1,3 +1,5 @@
switchView("mainMenuView");
const socket = io(); const socket = io();
// Handling server-sent events // Handling server-sent events
@ -142,6 +144,12 @@ $("#pvpMenuButton").on("click", function () {
switchView('pvpMenuView'); switchView('pvpMenuView');
}); });
$("#logout").on("click", function() {
lockUI(true);
socket.emit("logout");
window.location.reload();
});
const form = document.getElementById('pvpJoinForm'); const form = document.getElementById('pvpJoinForm');
const input = document.getElementById('pvpJoinCode'); const input = document.getElementById('pvpJoinCode');

View File

@ -23,10 +23,10 @@ function switchView(viewContainerId, useReplaceState=false) {
function lockUI(doLock) { function lockUI(doLock) {
if (doLock) { if (doLock) {
$("body").css("pointer-events", "none"); $("body").css("pointer-events", "none");
$("body").css("opacity", "0.4"); $(".container").css("opacity", "0.4");
} else { } else {
$("body").css("pointer-events", "inherit"); $("body").css("pointer-events", "inherit");
$("body").css("opacity", "1"); $(".container").css("opacity", "1");
} }
} }

View File

@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url';
import geoip from 'geoip-lite'; import geoip from 'geoip-lite';
import mysql from 'mysql'; import mysql from 'mysql';
import readline from "node:readline"; import { Lang } from './localisation.js';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@ -105,6 +105,8 @@ export class MailAuth {
startVerification(email, ip, agent, langId) { startVerification(email, ip, agent, langId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const conn = mysql.createConnection(this.mysqlOptions); const conn = mysql.createConnection(this.mysqlOptions);
const lang = new Lang([langId]);
conn.query(`SELECT user_id, nickname FROM accounts WHERE email = ${conn.escape(email)}`, async (error, response) => { conn.query(`SELECT user_id, nickname FROM accounts WHERE email = ${conn.escape(email)}`, async (error, response) => {
if (error) { reject(error); conn.end(); return; } if (error) { reject(error); conn.end(); return; }
if (response.length !== 0) { if (response.length !== 0) {
@ -138,13 +140,18 @@ export class MailAuth {
const lookup = geoip.lookup(ip); const lookup = geoip.lookup(ip);
const lookupData = `User-Agent: ${agent}\nAdres IP: ${ip}\nKraj: ${lookup.country}\nRegion: ${lookup.region}\nMiasto: ${lookup.city}`; var lookupData;
if (lookup) {
lookupData = `User-Agent: ${agent}\nAdres IP: ${ip}\nKraj: ${lookup.country}\nRegion: ${lookup.region}\nMiasto: ${lookup.city}`;
} else {
lookupData = `IP lookup failed`;
}
try { try {
await this.mail.sendMail({ await this.mail.sendMail({
from: this.mailFrom, from: this.mailFrom,
to: email, to: email,
subject: `${authCode} to twój kod autoryzacji do Statków`, subject: lang.t('email.This is your Statki authorisation code').replace("%s", authCode),
html: html.replace("{{ CODE }}", authCode).replace("{{ LOOKUP }}", lookupData), html: html.replace("{{ CODE }}", authCode).replace("{{ LOOKUP }}", lookupData),
}); });
} catch (e) { } catch (e) {
@ -173,13 +180,18 @@ export class MailAuth {
const lookup = geoip.lookup(ip); const lookup = geoip.lookup(ip);
const lookupData = `User-Agent: ${agent}\nIP address: ${ip}\nCountry: ${lookup.country}\nRegion: ${lookup.region}\nCity: ${lookup.city}`; var lookupData;
if (lookup) {
lookupData = `User-Agent: ${agent}\nAdres IP: ${ip}\nKraj: ${lookup.country}\nRegion: ${lookup.region}\nMiasto: ${lookup.city}`;
} else {
lookupData = `IP lookup failed`;
}
try { try {
await this.mail.sendMail({ await this.mail.sendMail({
from: this.mailFrom, from: this.mailFrom,
to: email, to: email,
subject: `${authCode} to twój kod logowania do Statków`, subject: lang.t('email.This is your Statki authorisation code').replace("%s", authCode),
html: html.replace("{{ NICKNAME }}", row.nickname).replace("{{ CODE }}", authCode).replace("{{ LOOKUP }}", lookupData), html: html.replace("{{ NICKNAME }}", row.nickname).replace("{{ CODE }}", authCode).replace("{{ LOOKUP }}", lookupData),
}); });
} catch (e) { } catch (e) {

View File

@ -100,6 +100,8 @@
<select name="language" id="languages"> <select name="language" id="languages">
</select> </select>
<h3><a href="/privacy" target="_blank">{{ t 'landing.Privacy policy' }}</a></h3>
<h3 id="logout">{{ t 'menu.Settings.Log out' }}</h3>
</div> </div>
</div> </div>

View File

@ -41,7 +41,7 @@
<div> <div>
<form action="#" method="post" id="loginForm"> <form action="#" method="post" id="loginForm">
<input type="email" name="email" placeholder="{{ t 'login.E-mail address' }}" <input type="email" name="email" placeholder="{{ t 'login.E-mail address' }}"
style="font-size: 1rem;"> style="font-size: 1rem;" id="email">
<input type="submit" value="{{ t 'login.Proceed' }}"> <input type="submit" value="{{ t 'login.Proceed' }}">
</form> </form>
</div> </div>
@ -55,9 +55,9 @@
<div class="modes"> <div class="modes">
<div> <div>
<p>{{ t 'auth.Enter the code sent to your e-mail box' }}</p> <p>{{ t 'auth.Enter the code sent to your e-mail box' }}</p>
<form action="/api/auth" method="post"> <form action="/api/auth" method="post" id="authForm">
<input type="text" name="code" placeholder="{{ t 'auth.Code' }}" style="font-size: 1rem;" <input type="text" name="code" placeholder="{{ t 'auth.Code' }}" style="font-size: 1rem;"
minlength="8" maxlength="10" autocomplete="off"> minlength="8" maxlength="10" autocomplete="off" id="authcode">
<input type="submit" value="{{ t 'auth.Login' }}"> <input type="submit" value="{{ t 'auth.Login' }}">
</form> </form>
</div> </div>
@ -83,6 +83,9 @@
"Disconnected": "{{ t 'errors.Disconnected' }}", "Disconnected": "{{ t 'errors.Disconnected' }}",
"Try to refresh the page if this error reoccurs": "{{ t 'errors.Try to refresh the page if this error reoccurs' }}", "Try to refresh the page if this error reoccurs": "{{ t 'errors.Try to refresh the page if this error reoccurs' }}",
"Connection error": "{{ t 'errors.Connection error' }}", "Connection error": "{{ t 'errors.Connection error' }}",
"E-mail address is required": "{{ t 'landing.E-mail address is required' }}",
"Auth code is required": "{{ t 'landing.Auth code is required' }}",
}; };
</script> </script>
<script src="/assets/js/spa_lib.js"></script> <script src="/assets/js/spa_lib.js"></script>