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";