diff --git a/client/src/components/ChatRoom.svelte b/client/src/components/ChatRoom.svelte index 4f5e383a..04390a87 100644 --- a/client/src/components/ChatRoom.svelte +++ b/client/src/components/ChatRoom.svelte @@ -1,7 +1,7 @@
diff --git a/client/src/lib/firebase/firebase-functions.ts b/client/src/lib/firebase/firebase-functions.ts index cc1a775f..848b15b4 100644 --- a/client/src/lib/firebase/firebase-functions.ts +++ b/client/src/lib/firebase/firebase-functions.ts @@ -6,6 +6,7 @@ import type { StalkChatroomRequest, LobbySettingsRequest, KickBanRequest, + LeaveLobbyRequest, } from "./firebase-functions-types"; import type { ChangeAvatarData } from "./functions-types/avatar"; @@ -20,3 +21,4 @@ export const changeAvatar = httpsCallable(functions, "ch export const verifyExpiration = httpsCallable(functions, "verifyExpiration"); export const kick = httpsCallable(functions, "kick"); export const ban = httpsCallable(functions, "ban"); +export const removeFromChatroom = httpsCallable(functions, "removeFromChatroom"); diff --git a/client/src/routes/game/+page.svelte b/client/src/routes/game/+page.svelte index d8e33b08..d09de129 100644 --- a/client/src/routes/game/+page.svelte +++ b/client/src/routes/game/+page.svelte @@ -10,6 +10,8 @@ import LobbyChat from "$components/LobbyChat.svelte"; import Header from "$components/Header.svelte"; import LobbySettings from "$components/LobbySettings.svelte"; + import IconButton from "@smui/icon-button"; + import Mdi from "$components/Mdi.svelte"; import { onSnapshot, doc, getDoc, type Unsubscribe } from "firebase/firestore"; import { onMount, onDestroy } from "svelte"; @@ -18,8 +20,9 @@ import { page } from "$app/stores"; import { authStore as user } from "$stores/auth"; import { goto } from "$app/navigation"; - import { verifyExpiration } from "$lib/firebase/firebase-functions"; + import { removeFromChatroom, verifyExpiration } from "$lib/firebase/firebase-functions"; import { formatTimer, DISPLAY_TIMERS } from "$lib/time"; + import { mdiArrowLeft } from "@mdi/js"; let lobbyCode: string | null = null; @@ -33,6 +36,8 @@ $: countdownVisible = lobby != undefined && DISPLAY_TIMERS[lobby.state] == true; let timer: ReturnType; + let spectatorChatroomId: string | undefined = undefined; + let errorMessage: string = ""; function updateCountdown() { @@ -161,6 +166,29 @@
+ + {#if lobbyCode !== null && lobby !== undefined && $user != null} + {#if lobby.state === "CHAT"} + {#if !lobby.alivePlayers.includes($user.uid) && spectatorChatroomId !== undefined} + { + // doesn't recognize the svelte if checks above + if (lobbyCode == null || spectatorChatroomId == undefined) { + return; + } + + await removeFromChatroom({ code: lobbyCode, chatId: spectatorChatroomId }); + // make it undefined because we don't want the back button to show on the select a chat view + spectatorChatroomId = undefined; + }} + > + + + {/if} + {/if} + {/if} + + {#if lobbyCode !== null && lobby !== undefined && $user != null} {#if lobby.state === "WAIT"} @@ -215,6 +243,9 @@ {lobbyCode} isStalker={privatePlayer.stalker} isSpectator={!lobby.alivePlayers.includes($user.uid)} + on:chatId={(event) => { + spectatorChatroomId = event.detail.chatRoomId; + }} /> {:else if lobby.state === "VOTE"} diff --git a/functions/src/chat.ts b/functions/src/chat.ts index ada1705e..92fe1f00 100644 --- a/functions/src/chat.ts +++ b/functions/src/chat.ts @@ -1,4 +1,5 @@ -import { Timestamp } from "firebase-admin/firestore"; +import * as functions from "firebase-functions"; +import { FieldValue, Timestamp } from "firebase-admin/firestore"; import type { DocumentReference, Transaction } from "firebase-admin/firestore"; import { db } from "./app"; import { @@ -7,8 +8,10 @@ import { getPrivatePlayerCollection, getLobbyChatCollection, getPromptAnswerCollection, + lobbyCollection, } from "./firestore-collections"; import { Lobby } from "./firestore-types/lobby"; +import { isStalkChatroomRequest as isLeaveLobbyRequest } from "./firebase-functions-types"; export async function deleteLobbyChatMessages(lobbyDoc: DocumentReference) { const messages = await getLobbyChatCollection(lobbyDoc).get(); @@ -69,3 +72,29 @@ export async function setAndDeleteAnswers( transaction.update(lobbyDoc, { state: "VOTE", expiration, players }); } + +export const removeFromChatroom = functions.https.onCall((data: unknown, context): Promise => { + const auth = context.auth; + if (auth === undefined) { + throw new functions.https.HttpsError("permission-denied", "User is not Authenticated"); + } + + if (!isLeaveLobbyRequest(data)) { + throw new functions.https.HttpsError("invalid-argument", "Data is not of isLeaveLobbyRequest type"); + } + + const lobbyDocRef = lobbyCollection.doc(data.code); + return db.runTransaction(async (transaction) => { + const chatRoomCollection = getChatRoomCollection(lobbyDocRef); + const chatRoomRef = chatRoomCollection.doc(data.chatId); + + const chatRoom = await transaction.get(chatRoomRef); + if (!chatRoom.exists) { + throw new functions.https.HttpsError("not-found", "Chatroom not found"); + } + + transaction.update(chatRoomRef, { + viewers: FieldValue.arrayRemove(auth.uid), + }); + }); +}); diff --git a/functions/src/firebase-functions-types.ts b/functions/src/firebase-functions-types.ts index 43bd3304..0de544ba 100644 --- a/functions/src/firebase-functions-types.ts +++ b/functions/src/firebase-functions-types.ts @@ -13,6 +13,8 @@ export type StalkChatroomRequest = { code: string; chatId: string }; export type KickBanRequest = { code: string; uid: string }; +export type LeaveLobbyRequest = StalkChatroomRequest; + export function isLobbyRequest(data: unknown): data is LobbyRequest { // will only return true if the data is an object with a code property and string return data != null && typeof data === "object" && "code" in data && typeof (data as LobbyRequest).code === "string";