mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-30 06:09:42 +00:00
validate usernames on change, fix public lobby highlight bug, validate
strings with zod for safety
This commit is contained in:
+15
-89
@@ -12,8 +12,6 @@ import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
|
||||
import { generateID } from "../core/Util";
|
||||
import { generateCryptoRandomUUID } from "./Utils";
|
||||
import { consolex } from "../core/Consolex";
|
||||
import {validateUsername} from "../core/validations/username";
|
||||
import {PublicLobby} from "./PublicLobby";
|
||||
|
||||
class Client {
|
||||
private gameStop: () => void
|
||||
@@ -26,7 +24,6 @@ class Client {
|
||||
|
||||
initialize(): void {
|
||||
this.usernameInput = document.querySelector('username-input') as UsernameInput;
|
||||
const usernameValidation = document.getElementById('username-error');
|
||||
if (!this.usernameInput) {
|
||||
consolex.warn('Username input element not found');
|
||||
}
|
||||
@@ -42,104 +39,38 @@ class Client {
|
||||
document.addEventListener('leave-lobby', this.handleLeaveLobby.bind(this));
|
||||
document.addEventListener('single-player', this.handleSinglePlayer.bind(this));
|
||||
|
||||
|
||||
const spModal = document.querySelector('single-player-modal') as SinglePlayerModal;
|
||||
spModal instanceof SinglePlayerModal
|
||||
document.getElementById('single-player').addEventListener('click', async () => {
|
||||
const username = this.usernameInput?.getCurrentUsername();
|
||||
|
||||
if (!username) {
|
||||
usernameValidation.textContent = 'Username is required';
|
||||
return;
|
||||
}
|
||||
|
||||
const isValid = await this.validateUsername(username);
|
||||
if (isValid) {
|
||||
document.getElementById('single-player').addEventListener('click', () => {
|
||||
if (this.usernameInput.isValid()) {
|
||||
spModal.open();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const hostModal = document.querySelector('host-lobby-modal') as HostPrivateLobbyModal;
|
||||
hostModal instanceof HostPrivateLobbyModal
|
||||
document.getElementById('host-lobby-button').addEventListener('click', async () => {
|
||||
const username = this.usernameInput?.getCurrentUsername();
|
||||
|
||||
if (!username) {
|
||||
usernameValidation.textContent = 'Username is required';
|
||||
return;
|
||||
}
|
||||
|
||||
const isValid = await this.validateUsername(username);
|
||||
|
||||
if (isValid) {
|
||||
document.getElementById('host-lobby-button').addEventListener('click', () => {
|
||||
if (this.usernameInput.isValid()) {
|
||||
hostModal.open();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
this.joinModal = document.querySelector('join-private-lobby-modal') as JoinPrivateLobbyModal;
|
||||
this.joinModal instanceof JoinPrivateLobbyModal
|
||||
|
||||
document.getElementById('join-private-lobby-button').addEventListener('click', async () => {
|
||||
const username = this.usernameInput?.getCurrentUsername();
|
||||
|
||||
if (!username) {
|
||||
usernameValidation.textContent = 'Username is required';
|
||||
return;
|
||||
}
|
||||
|
||||
const isValid = await this.validateUsername(username);
|
||||
|
||||
if (isValid) {
|
||||
document.getElementById('join-private-lobby-button').addEventListener('click', () => {
|
||||
if (this.usernameInput.isValid()) {
|
||||
this.joinModal.open();
|
||||
}else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
private async validateUsername(username: string): Promise<boolean> {
|
||||
this.usernameInput.validationError = '';
|
||||
|
||||
try {
|
||||
const { isValid, error } = validateUsername(username);
|
||||
|
||||
if (!isValid) {
|
||||
this.usernameInput.validationError = error || 'Failed to validate username.';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
consolex.error('Error validating username:', error);
|
||||
this.usernameInput.validationError = 'An error occurred while validating the username. Please try again.';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async handleJoinLobby(event: CustomEvent) {
|
||||
const lobby = event.detail.lobby;
|
||||
consolex.log(`Attempting to join lobby ${lobby.id}`);
|
||||
|
||||
// Validate the username
|
||||
const username = this.usernameInput?.getCurrentUsername();
|
||||
if (!username) {
|
||||
this.usernameInput.validationError = 'Username is required';
|
||||
return;
|
||||
}
|
||||
|
||||
const isValid = await this.validateUsername(username);
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop existing game
|
||||
const lobby = event.detail.lobby
|
||||
consolex.log(`joining lobby ${lobby.id}`)
|
||||
if (this.gameStop != null) {
|
||||
consolex.log('Stopping existing game before joining a new lobby');
|
||||
this.gameStop();
|
||||
consolex.log('joining lobby, stopping existing game')
|
||||
this.gameStop()
|
||||
}
|
||||
this.gameStop = joinLobby(
|
||||
{
|
||||
@@ -154,14 +85,9 @@ class Client {
|
||||
},
|
||||
() => this.joinModal.close()
|
||||
);
|
||||
const publicLobbyElement = document.querySelector('public-lobby') as PublicLobby;
|
||||
publicLobbyElement.highlightLobby();
|
||||
}
|
||||
|
||||
|
||||
private async handleLeaveLobby(event: CustomEvent) {
|
||||
const publicLobbyElement = document.querySelector('public-lobby') as PublicLobby;
|
||||
publicLobbyElement.highlightLobby();
|
||||
if (this.gameStop == null) {
|
||||
return
|
||||
}
|
||||
@@ -214,4 +140,4 @@ export function getPersistentIDFromCookie(): string {
|
||||
].join(';')
|
||||
|
||||
return newID
|
||||
}
|
||||
}
|
||||
@@ -104,13 +104,9 @@ export class PublicLobby extends LitElement {
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
highlightLobby() {
|
||||
this.isLobbyHighlighted = !this.isLobbyHighlighted;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
|
||||
private lobbyClicked(lobby: Lobby) {
|
||||
this.isLobbyHighlighted = !this.isLobbyHighlighted;
|
||||
if (this.currLobby == null) {
|
||||
this.currLobby = lobby
|
||||
this.dispatchEvent(new CustomEvent('join-lobby', {
|
||||
@@ -132,4 +128,4 @@ export class PublicLobby extends LitElement {
|
||||
this.currLobby = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-29
@@ -5,10 +5,10 @@ import { AllianceRequest, AllPlayers, Cell, GameType, Player, PlayerID, PlayerTy
|
||||
import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema, ClientPingMessageSchema, GameConfig, ClientLogMessageSchema } from "../core/Schemas"
|
||||
import { LobbyConfig } from "./GameRunner"
|
||||
import { LocalServer } from "./LocalServer"
|
||||
import {UsernameInput} from "./UsernameInput";
|
||||
import {HostLobbyModal as HostPrivateLobbyModal} from "./HostLobbyModal";
|
||||
import {JoinPrivateLobbyModal} from "./JoinPrivateLobbyModal";
|
||||
import {SinglePlayerModal} from "./SinglePlayerModal";
|
||||
import { UsernameInput } from "./UsernameInput";
|
||||
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
|
||||
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
|
||||
import { SinglePlayerModal } from "./SinglePlayerModal";
|
||||
|
||||
export class PauseGameEvent implements GameEvent {
|
||||
constructor(public readonly paused: boolean) { }
|
||||
@@ -185,14 +185,6 @@ export class Transport {
|
||||
this.socket.onmessage = (event: MessageEvent) => {
|
||||
try {
|
||||
const serverMsg = ServerMessageSchema.parse(JSON.parse(event.data));
|
||||
|
||||
// Handle validation errors
|
||||
if (serverMsg.type === 'validationError' && serverMsg.input === 'username-input') {
|
||||
this.handleValidationError(serverMsg.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process other message types
|
||||
this.onmessage(serverMsg);
|
||||
} catch (error) {
|
||||
console.error('Failed to process server message:', error);
|
||||
@@ -257,23 +249,6 @@ export class Transport {
|
||||
this.socket.onclose = (event: CloseEvent) => { }
|
||||
}
|
||||
|
||||
private handleValidationError(message: string) {
|
||||
const usernameInput = document.querySelector('username-input') as UsernameInput;
|
||||
const hostModal = document.querySelector('host-lobby-modal') as HostPrivateLobbyModal;
|
||||
const joinModal = document.querySelector('join-private-lobby-modal') as JoinPrivateLobbyModal;
|
||||
const singlePlayerModal = document.querySelector('single-player-modal') as SinglePlayerModal;
|
||||
|
||||
if (usernameInput) {
|
||||
this.closeAllModals([hostModal, joinModal, singlePlayerModal]);
|
||||
usernameInput.validationError = message;
|
||||
}
|
||||
}
|
||||
|
||||
private closeAllModals(modals: (HostPrivateLobbyModal | JoinPrivateLobbyModal | SinglePlayerModal)[]) {
|
||||
modals.forEach(modal => {
|
||||
modal.close();
|
||||
});
|
||||
}
|
||||
private onSendAllianceRequest(event: SendAllianceRequestIntentEvent) {
|
||||
this.sendIntent({
|
||||
type: "allianceRequest",
|
||||
|
||||
+37
-24
@@ -1,7 +1,7 @@
|
||||
import {LitElement, html, css} from 'lit';
|
||||
import {customElement, property, state} from 'lit/decorators.js';
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
import {MAX_USERNAME_LENGTH} from "../core/Util";
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { MAX_USERNAME_LENGTH, validateUsername } from '../core/validations/username';
|
||||
|
||||
const usernameKey: string = 'username';
|
||||
|
||||
@@ -10,6 +10,8 @@ export class UsernameInput extends LitElement {
|
||||
@state() private username: string = '';
|
||||
@property({ type: String }) validationError: string = '';
|
||||
|
||||
private _isValid: boolean = true
|
||||
|
||||
static styles = css`
|
||||
input {
|
||||
width: 100%;
|
||||
@@ -22,14 +24,12 @@ export class UsernameInput extends LitElement {
|
||||
line-height: 1.5;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
ring: 2px;
|
||||
ring-color: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #dc2626;
|
||||
background-color: #fff;
|
||||
@@ -47,30 +47,38 @@ export class UsernameInput extends LitElement {
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.username = this.getStoredUsername();
|
||||
this.dispatchUsernameEvent()
|
||||
this.dispatchUsernameEvent();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<input
|
||||
type="text"
|
||||
.value=${this.username}
|
||||
@input=${this.handleInput}
|
||||
placeholder="Enter your username"
|
||||
maxlength="${MAX_USERNAME_LENGTH}"
|
||||
>
|
||||
${this.validationError
|
||||
? html`<div class="error">${this.validationError}</div>`
|
||||
: null}
|
||||
`;
|
||||
<input
|
||||
type="text"
|
||||
.value=${this.username}
|
||||
@input=${this.handleChange}
|
||||
@change=${this.handleChange}
|
||||
placeholder="Enter your username"
|
||||
maxlength="${MAX_USERNAME_LENGTH}"
|
||||
>
|
||||
${this.validationError
|
||||
? html`<div class="error">${this.validationError}</div>`
|
||||
: null}
|
||||
`;
|
||||
}
|
||||
|
||||
private handleInput(e: Event) {
|
||||
private handleChange(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
this.username = input.value.trim();
|
||||
this.storeUsername(this.username);
|
||||
this.validationError = '';
|
||||
this.dispatchUsernameEvent();
|
||||
|
||||
|
||||
const result = validateUsername(this.username)
|
||||
this._isValid = result.isValid
|
||||
if (result.isValid) {
|
||||
this.storeUsername(this.username);
|
||||
this.validationError = ''
|
||||
} else {
|
||||
this.validationError = result.error
|
||||
}
|
||||
}
|
||||
|
||||
private getStoredUsername(): string {
|
||||
@@ -89,7 +97,7 @@ export class UsernameInput extends LitElement {
|
||||
|
||||
private dispatchUsernameEvent() {
|
||||
this.dispatchEvent(new CustomEvent('username-change', {
|
||||
detail: {username: this.username},
|
||||
detail: { username: this.username },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
@@ -108,4 +116,9 @@ export class UsernameInput extends LitElement {
|
||||
const threeDigits = decimal % 1000n;
|
||||
return threeDigits.toString().padStart(3, '0');
|
||||
}
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
return true
|
||||
return this._isValid
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user