import { LitElement, html } from "lit"; import { customElement, query, state } from "lit/decorators.js"; import { translateText } from "../client/Utils"; import { consolex } from "../core/Consolex"; import { GameInfo, GameRecord } from "../core/Schemas"; import { generateID } from "../core/Util"; import { getServerConfigFromClient } from "../core/configuration/ConfigLoader"; import { JoinLobbyEvent } from "./Main"; import "./components/baseComponents/Button"; import "./components/baseComponents/Modal"; @customElement("join-private-lobby-modal") export class JoinPrivateLobbyModal extends LitElement { @query("o-modal") private modalEl!: HTMLElement & { open: () => void; close: () => void; }; @query("#lobbyIdInput") private lobbyIdInput!: HTMLInputElement; @state() private message: string = ""; @state() private hasJoined = false; @state() private players: string[] = []; private playersInterval = null; render() { return html`
${this.message}
${this.hasJoined && this.players.length > 0 ? html`
${this.players.length} ${this.players.length === 1 ? translateText("private_lobby.player") : translateText("private_lobby.players")}
${this.players.map( (player) => html`${player}`, )}
` : ""}
${!this.hasJoined ? html` ` : ""}
`; } createRenderRoot() { return this; // light DOM } public open(id: string = "") { this.modalEl?.open(); if (id) { this.setLobbyId(id); this.joinLobby(); } } public close() { this.lobbyIdInput.value = null; this.modalEl?.close(); if (this.playersInterval) { clearInterval(this.playersInterval); this.playersInterval = null; } } public closeAndLeave() { this.close(); this.hasJoined = false; this.message = ""; this.dispatchEvent( new CustomEvent("leave-lobby", { detail: { lobby: this.lobbyIdInput.value }, bubbles: true, composed: true, }), ); } private setLobbyId(id: string) { if (id.startsWith("http")) { this.lobbyIdInput.value = id.split("join/")[1]; } else { this.lobbyIdInput.value = id; } } private handleChange(e: Event) { const value = (e.target as HTMLInputElement).value.trim(); this.setLobbyId(value); } private async pasteFromClipboard() { try { const clipText = await navigator.clipboard.readText(); let lobbyId: string; if (clipText.startsWith("http")) { lobbyId = clipText.split("join/")[1]; } else { lobbyId = clipText; } this.lobbyIdInput.value = lobbyId; } catch (err) { consolex.error("Failed to read clipboard contents: ", err); } } private async joinLobby(): Promise { const lobbyId = this.lobbyIdInput.value; consolex.log(`Joining lobby with ID: ${lobbyId}`); this.message = `${translateText("private_lobby.checking")}`; try { // First, check if the game exists in active lobbies const gameExists = await this.checkActiveLobby(lobbyId); if (gameExists) return; // If not active, check archived games const archivedGame = await this.checkArchivedGame(lobbyId); if (archivedGame) return; this.message = `${translateText("private_lobby.not_found")}`; } catch (error) { consolex.error("Error checking lobby existence:", error); this.message = `${translateText("private_lobby.error")}`; } } private async checkActiveLobby(lobbyId: string): Promise { const config = await getServerConfigFromClient(); const url = `/${config.workerPath(lobbyId)}/api/game/${lobbyId}/exists`; const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); const gameInfo = await response.json(); if (gameInfo.exists) { this.message = translateText("private_lobby.joined_waiting"); this.hasJoined = true; this.dispatchEvent( new CustomEvent("join-lobby", { detail: { gameID: lobbyId, clientID: generateID(), } as JoinLobbyEvent, bubbles: true, composed: true, }), ); this.playersInterval = setInterval(() => this.pollPlayers(), 1000); return true; } return false; } private async checkArchivedGame(lobbyId: string): Promise { const config = await getServerConfigFromClient(); const archiveUrl = `/${config.workerPath(lobbyId)}/api/archived_game/${lobbyId}`; const archiveResponse = await fetch(archiveUrl, { method: "GET", headers: { "Content-Type": "application/json" }, }); const archiveData = await archiveResponse.json(); if ( archiveData.success === false && archiveData.error === "Version mismatch" ) { consolex.warn( `Git commit hash mismatch for game ${lobbyId}`, archiveData.details, ); this.message = "This game was created with a different version. Cannot join."; return true; } if (archiveData.exists) { const gameRecord = archiveData.gameRecord as GameRecord; this.dispatchEvent( new CustomEvent("join-lobby", { detail: { gameID: lobbyId, gameRecord: gameRecord, clientID: generateID(), } as JoinLobbyEvent, bubbles: true, composed: true, }), ); return true; } return false; } private async pollPlayers() { if (!this.lobbyIdInput?.value) return; const config = await getServerConfigFromClient(); fetch( `/${config.workerPath(this.lobbyIdInput.value)}/api/game/${this.lobbyIdInput.value}`, { method: "GET", headers: { "Content-Type": "application/json", }, }, ) .then((response) => response.json()) .then((data: GameInfo) => { this.players = data.clients.map((p) => p.username); }) .catch((error) => { consolex.error("Error polling players:", error); }); } }