mirror of
https://github.com/HexCardGames/HexDeck.git
synced 2025-09-05 03:08:39 +02:00
feat: implement room joining by link and add dedicated room-utils store
This commit is contained in:
@ -8,22 +8,14 @@
|
|||||||
Helper,
|
Helper,
|
||||||
} from "flowbite-svelte";
|
} from "flowbite-svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import { sessionStore } from "/stores/sessionStore";
|
import { loading, join_error, create_error, rejoinRoomCode, rejoinRoomSessionData, requestJoinRoom, requestCreateRoom, joinSession, checkSessionData } from "../stores/roomStore";
|
||||||
|
|
||||||
let loading: "join" | "create" | false = false;
|
|
||||||
let rejoinRoomCode = "";
|
|
||||||
let rejoinRoomSessionData = {
|
|
||||||
sessionToken: "",
|
|
||||||
userId: "",
|
|
||||||
};
|
|
||||||
let joinRoomId = "";
|
let joinRoomId = "";
|
||||||
let join_error: string | false = false;
|
|
||||||
let create_error: string | false = false;
|
|
||||||
let inputRef: HTMLInputElement | null = null;
|
let inputRef: HTMLInputElement | null = null;
|
||||||
|
|
||||||
function formatInput(event: any) {
|
function formatInput(event: any) {
|
||||||
let rawValue = event.target.value.replace(/\D/g, "");
|
let rawValue = event.target.value.replace(/\D/g, "");
|
||||||
join_error = false;
|
join_error.set(false);
|
||||||
|
|
||||||
if (rawValue.length > 6) {
|
if (rawValue.length > 6) {
|
||||||
rawValue = rawValue.slice(0, 6);
|
rawValue = rawValue.slice(0, 6);
|
||||||
@ -36,217 +28,27 @@
|
|||||||
joinRoomId = formattedValue;
|
joinRoomId = formattedValue;
|
||||||
|
|
||||||
if (joinRoomId.length > 6) {
|
if (joinRoomId.length > 6) {
|
||||||
requestJoinRoom();
|
requestJoinRoom(joinRoomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(event: any) {
|
function handleKeyDown(event: any) {
|
||||||
if (event.key === "Backspace") {
|
if (event.key === "Backspace") {
|
||||||
join_error = false;
|
join_error.set(false);
|
||||||
|
|
||||||
let cursorPosition = event.target.selectionStart;
|
let cursorPosition = event.target.selectionStart;
|
||||||
|
|
||||||
if (cursorPosition === 4) {
|
if (cursorPosition === 4) {
|
||||||
// If cursor is at "-" position, delete the number before it
|
|
||||||
joinRoomId = joinRoomId.slice(0, 2);
|
joinRoomId = joinRoomId.slice(0, 2);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestJoinRoom(joinCode = joinRoomId) {
|
|
||||||
if (loading) return;
|
|
||||||
loading = "join";
|
|
||||||
join_error = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const controller = new AbortController();
|
|
||||||
// 5s timeout
|
|
||||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
||||||
|
|
||||||
const response = await fetch(`/api/room/join`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
JoinCode: joinCode.replaceAll("-", ""),
|
|
||||||
UsernameProposal: "UsernameProposal",
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timeout);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const data: { StatusCode: string; Message: string } =
|
|
||||||
await response.json();
|
|
||||||
// TODO i18n here on StatusCode if not use Message
|
|
||||||
if (["invalid_join_code"].includes(data?.StatusCode)) {
|
|
||||||
join_error = "no_room_found";
|
|
||||||
} else if (data?.Message) {
|
|
||||||
join_error = data?.Message;
|
|
||||||
} else {
|
|
||||||
throw new Error("Server error");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: {
|
|
||||||
SessionToken: string;
|
|
||||||
PlayerId: string;
|
|
||||||
Username: string;
|
|
||||||
Permissions: any;
|
|
||||||
} = await response.json();
|
|
||||||
const SessionToken = data.SessionToken;
|
|
||||||
const UserId = data.PlayerId;
|
|
||||||
joinSession(SessionToken, UserId);
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error.name === "AbortError") {
|
|
||||||
join_error = "timeout";
|
|
||||||
} else {
|
|
||||||
join_error = "request_failed";
|
|
||||||
}
|
|
||||||
console.error("Error joining room: ", error);
|
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
focusInput();
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function requestCreateRoom() {
|
|
||||||
if (loading) return;
|
|
||||||
loading = "create";
|
|
||||||
create_error = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const controller = new AbortController();
|
|
||||||
// 5s timeout
|
|
||||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
||||||
|
|
||||||
const response = await fetch(`/api/room/create`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
UsernameProposal: "UsernameProposal",
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timeout);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Server error");
|
|
||||||
}
|
|
||||||
|
|
||||||
const data:
|
|
||||||
| {
|
|
||||||
SessionToken: string;
|
|
||||||
PlayerId: string;
|
|
||||||
Username: string;
|
|
||||||
Permissions: any;
|
|
||||||
}
|
|
||||||
| { error: string } = await response.json();
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const SessionToken = data.SessionToken;
|
|
||||||
const UserId = data.PlayerId;
|
|
||||||
sessionStore.connect(SessionToken, UserId);
|
|
||||||
} else {
|
|
||||||
create_error = data.error || "room_creation_failed";
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
if (error.name === "AbortError") {
|
|
||||||
create_error = "timeout";
|
|
||||||
} else {
|
|
||||||
create_error = String(error);
|
|
||||||
}
|
|
||||||
console.error("Error creating room:", error);
|
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusInput() {
|
function focusInput() {
|
||||||
inputRef?.focus();
|
inputRef?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function joinSession(sessionToken: string, userId: string) {
|
|
||||||
try {
|
|
||||||
sessionStore.connect(sessionToken, userId);
|
|
||||||
} catch (error: any) {
|
|
||||||
join_error = "request_failed";
|
|
||||||
console.error("Error joining room session: ", error);
|
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
focusInput();
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkSessionToken(
|
|
||||||
sessionToken: string | undefined,
|
|
||||||
): Promise<boolean> {
|
|
||||||
if (!sessionToken) return false;
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
sessionToken: sessionToken,
|
|
||||||
});
|
|
||||||
const res = await fetch(`/api/check/session?${params}`);
|
|
||||||
return res.status == 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkJoinCode(
|
|
||||||
joinCode: string | undefined,
|
|
||||||
): Promise<boolean> {
|
|
||||||
if (!joinCode) return false;
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
JoinCode: joinCode,
|
|
||||||
});
|
|
||||||
const res = await fetch(`/api/check/joinCode?${params}`);
|
|
||||||
return res.status == 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkSessionData() {
|
|
||||||
const currentSessionData: {
|
|
||||||
sessionToken?: string;
|
|
||||||
userId?: string;
|
|
||||||
joinCode?: string;
|
|
||||||
} = JSON.parse(localStorage.getItem("currentSessionIds") || "{}");
|
|
||||||
if (await checkSessionToken(currentSessionData.sessionToken)) {
|
|
||||||
// Session is still valid
|
|
||||||
rejoinRoomSessionData = currentSessionData as any;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (await checkJoinCode(currentSessionData.joinCode)) {
|
|
||||||
// joinCode is still valid
|
|
||||||
rejoinRoomCode = currentSessionData.joinCode as string;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastSessionData: {
|
|
||||||
sessionToken?: string;
|
|
||||||
userId?: string;
|
|
||||||
joinCode?: string;
|
|
||||||
} = JSON.parse(localStorage.getItem("lastSessionIds") || "{}");
|
|
||||||
|
|
||||||
if (await checkSessionToken(lastSessionData.sessionToken)) {
|
|
||||||
// Session is still valid
|
|
||||||
rejoinRoomSessionData = lastSessionData as any;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (await checkJoinCode(lastSessionData.joinCode)) {
|
|
||||||
// joinCode is still valid
|
|
||||||
rejoinRoomCode = lastSessionData.joinCode as string;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// focus room code input on mount
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
focusInput();
|
focusInput();
|
||||||
checkSessionData();
|
checkSessionData();
|
||||||
@ -254,19 +56,19 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full max-w-md">
|
<div class="w-full max-w-md">
|
||||||
{#if rejoinRoomSessionData?.sessionToken}
|
{#if $rejoinRoomSessionData?.sessionToken}
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<Button
|
<Button
|
||||||
class="bg-primary-400 focus:bg-primary-100 dark:bg-primary-800 focus:dark:bg-primary-700 focus:ring-0 text-dark w-full overflow-hidden rounded-xl border-1 border-primary-800"
|
class="bg-primary-400 focus:bg-primary-100 dark:bg-primary-800 focus:dark:bg-primary-700 focus:ring-0 text-dark w-full overflow-hidden rounded-xl border-1 border-primary-800"
|
||||||
size="lg"
|
size="lg"
|
||||||
disabled={loading && loading != "create"}
|
disabled={$loading && $loading != "create"}
|
||||||
on:click={() =>
|
on:click={() =>
|
||||||
joinSession(
|
joinSession(
|
||||||
rejoinRoomSessionData?.sessionToken,
|
$rejoinRoomSessionData?.sessionToken,
|
||||||
rejoinRoomSessionData?.userId,
|
$rejoinRoomSessionData?.userId,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{#if loading == "create"}
|
{#if $loading == "create"}
|
||||||
<Spinner
|
<Spinner
|
||||||
class="text-primary-350 dark:text-primary-200 me-3"
|
class="text-primary-350 dark:text-primary-200 me-3"
|
||||||
size="4"
|
size="4"
|
||||||
@ -274,12 +76,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{$_("landing_page.connect_room.rejoin_last_room")}
|
{$_("landing_page.connect_room.rejoin_last_room")}
|
||||||
</Button>
|
</Button>
|
||||||
{#if create_error}
|
{#if $create_error}
|
||||||
<Helper class="mt-2">
|
<Helper class="mt-2">
|
||||||
<span class="text-red-900 dark:text-red-300 text-sm">
|
<span class="text-red-900 dark:text-red-300 text-sm">
|
||||||
{$_(`error_messages.${create_error}`, {
|
{$_(`error_messages.${$create_error}`, {
|
||||||
default: $_("error_messages.error_message", {
|
default: $_("error_messages.error_message", {
|
||||||
values: { error_message: create_error },
|
values: { error_message: $create_error },
|
||||||
}),
|
}),
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
@ -290,15 +92,15 @@
|
|||||||
{$_("landing_page.connect_room.or")}
|
{$_("landing_page.connect_room.or")}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if rejoinRoomCode}
|
{#if $rejoinRoomCode}
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<Button
|
<Button
|
||||||
class="bg-primary-400 focus:bg-primary-100 dark:bg-primary-800 focus:dark:bg-primary-700 focus:ring-0 text-dark w-full overflow-hidden rounded-xl border-1 border-primary-800"
|
class="bg-primary-400 focus:bg-primary-100 dark:bg-primary-800 focus:dark:bg-primary-700 focus:ring-0 text-dark w-full overflow-hidden rounded-xl border-1 border-primary-800"
|
||||||
size="lg"
|
size="lg"
|
||||||
disabled={loading && loading != "create"}
|
disabled={$loading && $loading != "create"}
|
||||||
on:click={() => requestJoinRoom(rejoinRoomCode)}
|
on:click={() => requestJoinRoom($rejoinRoomCode)}
|
||||||
>
|
>
|
||||||
{#if loading == "create"}
|
{#if $loading == "create"}
|
||||||
<Spinner
|
<Spinner
|
||||||
class="text-primary-350 dark:text-primary-200 me-3"
|
class="text-primary-350 dark:text-primary-200 me-3"
|
||||||
size="4"
|
size="4"
|
||||||
@ -306,14 +108,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{$_("landing_page.connect_room.join_last_room")}
|
{$_("landing_page.connect_room.join_last_room")}
|
||||||
</Button>
|
</Button>
|
||||||
{#if create_error}
|
{#if $create_error}
|
||||||
<Helper class="mt-2">
|
<Helper class="mt-2">
|
||||||
<span class="text-red-900 dark:text-red-300 text-sm">
|
<span class="text-red-900 dark:text-red-300 text-sm">
|
||||||
{$_(`error_messages.${create_error}`, {
|
{$_(`error_messages.${$create_error}`, {
|
||||||
default: $_("error_messages.error_message", {
|
default: $_("error_messages.error_message", {
|
||||||
values: { error_message: create_error },
|
values: { error_message: $create_error },
|
||||||
}),
|
})})}
|
||||||
})}
|
|
||||||
</span>
|
</span>
|
||||||
</Helper>
|
</Helper>
|
||||||
{/if}
|
{/if}
|
||||||
@ -330,7 +131,7 @@
|
|||||||
<InputAddon
|
<InputAddon
|
||||||
class="w-10 text-center bg-primary-400 dark:bg-primary-800"
|
class="w-10 text-center bg-primary-400 dark:bg-primary-800"
|
||||||
>
|
>
|
||||||
{#if loading == "join"}
|
{#if $loading == "join"}
|
||||||
<div class="w-full flex justify-center">
|
<div class="w-full flex justify-center">
|
||||||
<Spinner
|
<Spinner
|
||||||
class="text-primary-350 dark:text-primary-200"
|
class="text-primary-350 dark:text-primary-200"
|
||||||
@ -344,23 +145,23 @@
|
|||||||
<input
|
<input
|
||||||
class="bg-primary-200 px-4 focus:bg-primary-100 text-black dark:text-white mr-md dark:bg-primary-700 dark:focus:bg-primary-600 w-full border-0 focus:outline-none h-12"
|
class="bg-primary-200 px-4 focus:bg-primary-100 text-black dark:text-white mr-md dark:bg-primary-700 dark:focus:bg-primary-600 w-full border-0 focus:outline-none h-12"
|
||||||
placeholder={$_("landing_page.connect_room.enter_room_code")}
|
placeholder={$_("landing_page.connect_room.enter_room_code")}
|
||||||
class:cursor-not-allowed={loading}
|
class:cursor-not-allowed={$loading}
|
||||||
class:opacity-50={loading}
|
class:opacity-50={$loading}
|
||||||
disabled={!!loading}
|
disabled={!!$loading}
|
||||||
bind:this={inputRef}
|
bind:this={inputRef}
|
||||||
bind:value={joinRoomId}
|
bind:value={joinRoomId}
|
||||||
on:input={formatInput}
|
on:input={formatInput}
|
||||||
on:keydown={handleKeyDown}
|
on:keydown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
{#if join_error}
|
{#if $join_error}
|
||||||
<Helper class="mt-2">
|
<Helper class="mt-2">
|
||||||
<span class="text-red-900 dark:text-red-300 text-sm">
|
<span class="text-red-900 dark:text-red-300 text-sm">
|
||||||
{$_(`error_messages.${join_error}`, {
|
{$_(`error_messages.${$join_error}`, {
|
||||||
default: $_("error_messages.error_message", {
|
default: $_("error_messages.error_message", {
|
||||||
values: { error_message: join_error },
|
values: { error_message: $join_error },
|
||||||
}),
|
})}
|
||||||
})}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</Helper>
|
</Helper>
|
||||||
{/if}
|
{/if}
|
||||||
@ -372,10 +173,10 @@
|
|||||||
<Button
|
<Button
|
||||||
class="bg-primary-400 focus:bg-primary-100 dark:bg-primary-800 focus:dark:bg-primary-700 focus:ring-0 text-dark w-full overflow-hidden rounded-xl border-1 border-primary-800"
|
class="bg-primary-400 focus:bg-primary-100 dark:bg-primary-800 focus:dark:bg-primary-700 focus:ring-0 text-dark w-full overflow-hidden rounded-xl border-1 border-primary-800"
|
||||||
size="lg"
|
size="lg"
|
||||||
disabled={loading && loading != "create"}
|
disabled={$loading && $loading != "create"}
|
||||||
on:click={requestCreateRoom}
|
on:click={requestCreateRoom}
|
||||||
>
|
>
|
||||||
{#if loading == "create"}
|
{#if $loading == "create"}
|
||||||
<Spinner
|
<Spinner
|
||||||
class="text-primary-350 dark:text-primary-200 me-3"
|
class="text-primary-350 dark:text-primary-200 me-3"
|
||||||
size="4"
|
size="4"
|
||||||
@ -383,14 +184,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{$_("landing_page.connect_room.create_a_room")}
|
{$_("landing_page.connect_room.create_a_room")}
|
||||||
</Button>
|
</Button>
|
||||||
{#if create_error}
|
{#if $create_error}
|
||||||
<Helper class="mt-2">
|
<Helper class="mt-2">
|
||||||
<span class="text-red-900 dark:text-red-300 text-sm">
|
<span class="text-red-900 dark:text-red-300 text-sm">
|
||||||
{$_(`error_messages.${create_error}`, {
|
{$_(`error_messages.${$create_error}`, {
|
||||||
default: $_("error_messages.error_message", {
|
default: $_("error_messages.error_message", {
|
||||||
values: { error_message: create_error },
|
values: { error_message: $create_error },
|
||||||
}),
|
})})}
|
||||||
})}
|
|
||||||
</span>
|
</span>
|
||||||
</Helper>
|
</Helper>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
function copyGameLinkToClipboard() {
|
function copyGameLinkToClipboard() {
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
.writeText(
|
.writeText(
|
||||||
`${window.location.origin}/join/${$sessionStore.joinCode}`,
|
`${window.location.origin}/Game?join=${$sessionStore.joinCode}`,
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
copied = true;
|
copied = true;
|
||||||
@ -70,6 +70,10 @@
|
|||||||
sessionStore.leaveRoom();
|
sessionStore.leaveRoom();
|
||||||
showLeaveModal = false;
|
showLeaveModal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
// TODO start game
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Modal: Confirm Leave Room -->
|
<!-- Modal: Confirm Leave Room -->
|
||||||
@ -173,6 +177,7 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- TODO Grid not fully responsive -->
|
||||||
<div class="grid md:grid-flow-col grid-flow-row justify-center mt-6 mb-2 gap-4">
|
<div class="grid md:grid-flow-col grid-flow-row justify-center mt-6 mb-2 gap-4">
|
||||||
<!-- Rename (This) Player -->
|
<!-- Rename (This) Player -->
|
||||||
{#if sessionStore.isConnected()}
|
{#if sessionStore.isConnected()}
|
||||||
@ -235,11 +240,11 @@
|
|||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<!-- Start game button -->
|
<!-- Start game button -->
|
||||||
{#if sessionStore.getPlayerPermissions().isHost}
|
{#if sessionStore.getPlayerPermissions().isHost && sessionStore.getState().gameState === GameState.Lobby}
|
||||||
<Button
|
<Button
|
||||||
class="w-xs mx-auto text-dark bg-green-200 dark:bg-green-900 hover:bg-green-200 dark:hover:bg-green-900 backdrop-blur-lg border border-black/20 dark:border-white/20 shadow-lg p-4 rounded-2xl flex items-center justify-between transition-all cursor-pointer"
|
class="w-xs mx-auto text-dark bg-green-200 dark:bg-green-900 hover:bg-green-200 dark:hover:bg-green-900 backdrop-blur-lg border border-black/20 dark:border-white/20 shadow-lg p-4 rounded-2xl flex items-center justify-between transition-all cursor-pointer"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
copyGameCodeToClipboard();
|
startGame();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="grid justify-items-start">
|
<div class="grid justify-items-start">
|
||||||
@ -320,7 +325,7 @@
|
|||||||
<Button
|
<Button
|
||||||
outline={true}
|
outline={true}
|
||||||
color="alternative"
|
color="alternative"
|
||||||
class="p-2! text-red-800 hover:bg-red-500"
|
class="p-2! text-blue-800 hover:bg-blue-500"
|
||||||
size="lg"
|
size="lg"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
showRenameModal = true;
|
showRenameModal = true;
|
||||||
|
@ -5,8 +5,18 @@
|
|||||||
import { GameState, sessionStore } from "../stores/sessionStore";
|
import { GameState, sessionStore } from "../stores/sessionStore";
|
||||||
import { Spinner } from "flowbite-svelte";
|
import { Spinner } from "flowbite-svelte";
|
||||||
import { SvelteDate } from "svelte/reactivity";
|
import { SvelteDate } from "svelte/reactivity";
|
||||||
|
import { requestJoinRoom } from "../stores/roomStore";
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const joinParam = params.get('join');
|
||||||
|
console.log('Join param:', joinParam);
|
||||||
|
|
||||||
|
if(joinParam) {
|
||||||
|
await requestJoinRoom(joinParam);
|
||||||
|
// Maybe show message instead redirecting to / if the join was unsuccessful
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (!sessionStore.hasSessionData()) {
|
if (!sessionStore.hasSessionData()) {
|
||||||
console.warn("No sessionData found! Go back home.");
|
console.warn("No sessionData found! Go back home.");
|
||||||
window.history.replaceState({}, "", "/");
|
window.history.replaceState({}, "", "/");
|
||||||
|
146
frontend/src/stores/roomStore.ts
Normal file
146
frontend/src/stores/roomStore.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
import { sessionStore } from './sessionStore';
|
||||||
|
|
||||||
|
export const loading = writable<"join" | "create" | false>(false);
|
||||||
|
export const join_error = writable<string | false>(false);
|
||||||
|
export const create_error = writable<string | false>(false);
|
||||||
|
export const rejoinRoomCode = writable<string>("");
|
||||||
|
export const rejoinRoomSessionData = writable<{ sessionToken: string; userId: string }>({ sessionToken: "", userId: "" });
|
||||||
|
|
||||||
|
export async function requestJoinRoom(joinCode: string) {
|
||||||
|
loading.set("join");
|
||||||
|
join_error.set(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||||
|
|
||||||
|
const response = await fetch(`/api/room/join`, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
JoinCode: joinCode.replaceAll("-", ""),
|
||||||
|
UsernameProposal: "UsernameProposal",
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const data: { StatusCode: string; Message: string } = await response.json();
|
||||||
|
if (["invalid_join_code"].includes(data?.StatusCode)) {
|
||||||
|
join_error.set("no_room_found");
|
||||||
|
} else if (data?.Message) {
|
||||||
|
join_error.set(data?.Message);
|
||||||
|
} else {
|
||||||
|
throw new Error("Server error");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: { SessionToken: string; PlayerId: string; Username: string; Permissions: any } = await response.json();
|
||||||
|
const SessionToken = data.SessionToken;
|
||||||
|
const UserId = data.PlayerId;
|
||||||
|
joinSession(SessionToken, UserId);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.name === "AbortError") {
|
||||||
|
join_error.set("timeout");
|
||||||
|
} else {
|
||||||
|
join_error.set("request_failed");
|
||||||
|
}
|
||||||
|
console.error("Error joining room: ", error);
|
||||||
|
} finally {
|
||||||
|
loading.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requestCreateRoom() {
|
||||||
|
loading.set("create");
|
||||||
|
create_error.set(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||||
|
|
||||||
|
const response = await fetch(`/api/room/create`, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
UsernameProposal: "UsernameProposal",
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Server error");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: { SessionToken: string; PlayerId: string; Username: string; Permissions: any } = await response.json();
|
||||||
|
const SessionToken = data.SessionToken;
|
||||||
|
const UserId = data.PlayerId;
|
||||||
|
sessionStore.connect(SessionToken, UserId);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.name === "AbortError") {
|
||||||
|
create_error.set("timeout");
|
||||||
|
} else {
|
||||||
|
create_error.set(String(error));
|
||||||
|
}
|
||||||
|
console.error("Error creating room:", error);
|
||||||
|
} finally {
|
||||||
|
loading.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function joinSession(sessionToken: string, userId: string) {
|
||||||
|
try {
|
||||||
|
sessionStore.connect(sessionToken, userId);
|
||||||
|
} catch (error: any) {
|
||||||
|
join_error.set("request_failed");
|
||||||
|
console.error("Error joining room session: ", error);
|
||||||
|
} finally {
|
||||||
|
loading.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkSessionToken(sessionToken: string | undefined): Promise<boolean> {
|
||||||
|
if (!sessionToken) return false;
|
||||||
|
const params = new URLSearchParams({ sessionToken: sessionToken });
|
||||||
|
const res = await fetch(`/api/check/session?${params}`);
|
||||||
|
return res.status == 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkJoinCode(joinCode: string | undefined): Promise<boolean> {
|
||||||
|
if (!joinCode) return false;
|
||||||
|
const params = new URLSearchParams({ JoinCode: joinCode });
|
||||||
|
const res = await fetch(`/api/check/joinCode?${params}`);
|
||||||
|
return res.status == 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkSessionData() {
|
||||||
|
const currentSessionData: { sessionToken?: string; userId?: string; joinCode?: string } = JSON.parse(localStorage.getItem("currentSessionIds") || "{}");
|
||||||
|
if (await checkSessionToken(currentSessionData.sessionToken)) {
|
||||||
|
rejoinRoomSessionData.set(currentSessionData as any);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (await checkJoinCode(currentSessionData.joinCode)) {
|
||||||
|
rejoinRoomCode.set(currentSessionData.joinCode as string);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastSessionData: { sessionToken?: string; userId?: string; joinCode?: string } = JSON.parse(localStorage.getItem("lastSessionIds") || "{}");
|
||||||
|
if (await checkSessionToken(lastSessionData.sessionToken)) {
|
||||||
|
rejoinRoomSessionData.set(lastSessionData as any);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (await checkJoinCode(lastSessionData.joinCode)) {
|
||||||
|
rejoinRoomCode.set(lastSessionData.joinCode as string);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
@ -38,6 +38,7 @@ interface SessionData {
|
|||||||
interface RoomInfoObj {
|
interface RoomInfoObj {
|
||||||
RoomId: string;
|
RoomId: string;
|
||||||
JoinCode: string;
|
JoinCode: string;
|
||||||
|
TopCard: any;
|
||||||
GameState: GameState;
|
GameState: GameState;
|
||||||
CardDeckId: number;
|
CardDeckId: number;
|
||||||
Winner?: string;
|
Winner?: string;
|
||||||
|
Reference in New Issue
Block a user