From cc5279fc050636e50063b6d585032b7b20488f2a Mon Sep 17 00:00:00 2001
From: Robert Long <robert@robertlong.me>
Date: Mon, 9 Aug 2021 13:54:23 -0700
Subject: [PATCH] Disallow joining multiple times and properly clean up event
 listeners

---
 src/ConferenceCallManagerHooks.js | 86 +++++++++++++++++--------------
 src/Room.jsx                      | 16 ++++--
 2 files changed, 60 insertions(+), 42 deletions(-)

diff --git a/src/ConferenceCallManagerHooks.js b/src/ConferenceCallManagerHooks.js
index d123652..9d25452 100644
--- a/src/ConferenceCallManagerHooks.js
+++ b/src/ConferenceCallManagerHooks.js
@@ -131,15 +131,24 @@ export function useConferenceCallManager(homeserverUrl) {
 }
 
 export function useVideoRoom(manager, roomId, timeout = 5000) {
-  const [{ loading, joined, room, participants, error }, setState] = useState({
-    loading: true,
-    joined: false,
-    room: undefined,
-    participants: [],
-    error: undefined,
-  });
+  const [{ loading, joined, joining, room, participants, error }, setState] =
+    useState({
+      loading: true,
+      joining: false,
+      joined: false,
+      room: undefined,
+      participants: [],
+      error: undefined,
+    });
 
   useEffect(() => {
+    setState((prevState) => ({
+      ...prevState,
+      loading: true,
+      room: undefined,
+      error: undefined,
+    }));
+
     function onBeforeUnload(event) {
       manager.leaveCall();
     }
@@ -149,19 +158,14 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
 
     window.addEventListener(unloadEvent, onBeforeUnload);
 
-    return () => {
-      window.removeEventListener(unloadEvent, onBeforeUnload);
-      manager.leaveCall();
+    const onParticipantsChanged = () => {
+      setState((prevState) => ({
+        ...prevState,
+        participants: manager.participants,
+      }));
     };
-  }, [manager]);
 
-  useEffect(() => {
-    setState((prevState) => ({
-      ...prevState,
-      loading: true,
-      room: undefined,
-      error: undefined,
-    }));
+    manager.on("participants_changed", onParticipantsChanged);
 
     manager.client.joinRoom(roomId).catch((err) => {
       setState((prevState) => ({ ...prevState, loading: false, error: err }));
@@ -208,47 +212,41 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
 
     return () => {
       manager.client.removeListener("Room", roomCallback);
-      manager.leaveCall();
+      manager.removeListener("participants_changed", onParticipantsChanged);
+      window.removeEventListener(unloadEvent, onBeforeUnload);
       clearTimeout(timeoutId);
+      manager.leaveCall();
     };
-  }, [roomId]);
+  }, [manager, roomId]);
 
   const joinCall = useCallback(() => {
-    const onParticipantsChanged = () => {
-      setState((prevState) => ({
-        ...prevState,
-        participants: manager.participants,
-      }));
-    };
+    if (joining || joined) {
+      return;
+    }
 
-    manager.on("participants_changed", onParticipantsChanged);
+    setState((prevState) => ({
+      ...prevState,
+      joining: true,
+    }));
 
     manager
       .enter(roomId)
       .then(() => {
         setState((prevState) => ({
           ...prevState,
+          joining: false,
           joined: true,
         }));
       })
       .catch((error) => {
         setState((prevState) => ({
           ...prevState,
+          joining: false,
           joined: false,
           error,
         }));
       });
-
-    return () => {
-      manager.removeListener("participants_changed", onParticipantsChanged);
-
-      setState((prevState) => ({
-        ...prevState,
-        joined: false,
-        participants: [],
-      }));
-    };
-  }, [manager, roomId]);
+  }, [manager, roomId, joining, joined]);
 
   const leaveCall = useCallback(() => {
     manager.leaveCall();
@@ -257,10 +255,20 @@ export function useVideoRoom(manager, roomId, timeout = 5000) {
       ...prevState,
       participants: manager.participants,
       joined: false,
+      joining: false,
     }));
   }, [manager]);
 
-  return { loading, joined, room, participants, error, joinCall, leaveCall };
+  return {
+    loading,
+    joined,
+    joining,
+    room,
+    participants,
+    error,
+    joinCall,
+    leaveCall,
+  };
 }
 
 export function useRooms(manager) {
diff --git a/src/Room.jsx b/src/Room.jsx
index 47204dd..12075d4 100644
--- a/src/Room.jsx
+++ b/src/Room.jsx
@@ -28,8 +28,16 @@ function useQuery() {
 export function Room({ manager }) {
   const { roomId } = useParams();
   const query = useQuery();
-  const { loading, joined, room, participants, error, joinCall, leaveCall } =
-    useVideoRoom(manager, roomId);
+  const {
+    loading,
+    joined,
+    joining,
+    room,
+    participants,
+    error,
+    joinCall,
+    leaveCall,
+  } = useVideoRoom(manager, roomId);
   const debugStr = query.get("debug");
   const [debug, setDebug] = useState(debugStr === "" || debugStr === "true");
 
@@ -77,7 +85,9 @@ export function Room({ manager }) {
               <li key={member.userId}>{member.name}</li>
             ))}
           </ul>
-          <button onClick={joinCall}>Join Call</button>
+          <button disabled={joining} onClick={joinCall}>
+            Join Call
+          </button>
         </div>
       )}
       {!loading && room && joined && participants.length === 0 && (