diff --git a/frontend/package.json b/frontend/package.json index 676e042..19a0215 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@jitsi/react-sdk": "^1.3.0", + "just-curry-it": "^5.3.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d8591f3..577ca17 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,14 +1,29 @@ import "./App.css"; import Meeting from "./components/meeting/Meeting"; import Sidebar from "./components/sidebar/Sidebar"; +import useBackendData from "./hooks/useBackendData"; +import useConferenceData from "./hooks/useConferenceData"; +import useLocalUser from "./hooks/useLocalUser"; function App() { - return ( -
- - -
+ const { userInfo, setUserInfo } = useLocalUser(); + const { roomData, sendMessage } = useBackendData(userInfo); + const { conferenceData, setConferenceData } = useConferenceData( + sendMessage, + setUserInfo ); + + console.log(roomData); + + if (roomData && userInfo) { + return ( +
+ + +
+ ); + } + return

🌀 Loading...

; } export default App; diff --git a/frontend/src/background/constants.ts b/frontend/src/background/constants.ts index 30ea1c9..01ea4ca 100644 --- a/frontend/src/background/constants.ts +++ b/frontend/src/background/constants.ts @@ -1,4 +1,14 @@ -const JITSI_DOMAIN = "thisisnotajitsi.filefighter.de"; -const WEBSOCKET_URL = "ws" + (window.location.protocol == "https:" ? "s" : "") + "://" + window.location.host + "/ws" +const ISPROD = window.location.protocol == "https:"; +const JITSI_DOMAIN = ISPROD + ? "thisisnotajitsi.filefighter.de" + : "localhost:8443"; +const WEBSOCKET_URL = + "ws" + + (ISPROD ? "s" : "") + + "://" + + (ISPROD ? window.location.host : "localhost:9160") + + "/ws"; -export { JITSI_DOMAIN, WEBSOCKET_URL }; +const USER_COOKIE_NAME = "jitsi-rooms-user"; + +export { JITSI_DOMAIN, WEBSOCKET_URL, USER_COOKIE_NAME }; diff --git a/frontend/src/background/cookies.ts b/frontend/src/background/cookies.ts new file mode 100644 index 0000000..a758c61 --- /dev/null +++ b/frontend/src/background/cookies.ts @@ -0,0 +1,24 @@ +function getCookie(cname: string) { + let name = cname + "="; + let decodedCookie = decodeURIComponent(document.cookie); + let ca = decodedCookie.split(";"); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == " ") { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return ""; +} + +function setCookie(cname: string, cvalue: string, exdays = 30) { + const d = new Date(); + d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); + let expires = "expires=" + d.toUTCString(); + document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; +} + +export { getCookie, setCookie }; diff --git a/frontend/src/background/jitsi/eventListeners.ts b/frontend/src/background/jitsi/eventListeners.ts new file mode 100644 index 0000000..ff56e8d --- /dev/null +++ b/frontend/src/background/jitsi/eventListeners.ts @@ -0,0 +1,16 @@ +export interface ConferenceData { + roomName: string; // the room name of the conference + id: string; // the id of the local participant + displayName: string; // the display name of the local participant + avatarURL: string; // the avatar URL of the local participant + breakoutRoom: boolean; // whether the current room is a breakout room +} + +function videoConferenceJoinedListener( + setConferenceData: (newData: ConferenceData) => void, + videoConferenceJoinedEvent: ConferenceData +) { + setConferenceData(videoConferenceJoinedEvent); +} + +export { videoConferenceJoinedListener }; diff --git a/frontend/src/background/types/roomData.ts b/frontend/src/background/types/roomData.ts new file mode 100644 index 0000000..02ae94d --- /dev/null +++ b/frontend/src/background/types/roomData.ts @@ -0,0 +1,16 @@ +export interface RoomData { + roomName: string; + participants: Participant[]; +} + +export interface Participant { + avatarURL: string; + displayName: string; + jid: string; + email: string; +} + +export interface UsersData { + roomsData: RoomData[]; + usersWithOutRoom: string[]; +} diff --git a/frontend/src/components/jitsi/JitsiEntrypoint.tsx b/frontend/src/components/jitsi/JitsiEntrypoint.tsx index 9fe42c2..9c65347 100644 --- a/frontend/src/components/jitsi/JitsiEntrypoint.tsx +++ b/frontend/src/components/jitsi/JitsiEntrypoint.tsx @@ -1,13 +1,19 @@ import { JitsiMeeting } from "@jitsi/react-sdk"; import { JITSI_DOMAIN } from "../../background/constants"; import { UserInfo } from "./types"; +import curry from "just-curry-it"; +import { + ConferenceData, + videoConferenceJoinedListener, +} from "../../background/jitsi/eventListeners"; interface Props { roomName: string; userInfo: UserInfo; + setConferenceData: (newData: ConferenceData) => void; } -function JitsiEntrypoint({ roomName, userInfo }: Props) { +function JitsiEntrypoint({ roomName, userInfo, setConferenceData }: Props) { return ( { // here you can attach custom event listeners to the Jitsi Meet External API // you can also store it locally to execute commands + + externalApi.addEventListener( + "videoConferenceJoined", + curry(videoConferenceJoinedListener)(setConferenceData) + ); }} getIFrameRef={(iframeRef) => { iframeRef.style.height = "100%"; diff --git a/frontend/src/components/jitsi/types.ts b/frontend/src/components/jitsi/types.ts index 4021597..85852ef 100644 --- a/frontend/src/components/jitsi/types.ts +++ b/frontend/src/components/jitsi/types.ts @@ -1,7 +1,4 @@ - -interface UserInfo { +export interface UserInfo { displayName: string; - email: string + email: string; } - -export { UserInfo } diff --git a/frontend/src/components/meeting/Meeting.tsx b/frontend/src/components/meeting/Meeting.tsx index 5ce9307..dd9ec6a 100644 --- a/frontend/src/components/meeting/Meeting.tsx +++ b/frontend/src/components/meeting/Meeting.tsx @@ -1,23 +1,32 @@ import { useCallback, useState } from "react"; +import { ConferenceData } from "../../background/jitsi/eventListeners"; import { useRoomName } from "../../hooks/useRoomName"; import JitsiEntrypoint from "../jitsi/JitsiEntrypoint"; import { UserInfo } from "../jitsi/types"; import MeetingNameInput from "./MeetingNameInput"; -function Meeting() { +interface Props { + setConferenceData: (newData: ConferenceData) => void; + userInfo: UserInfo; +} + +function Meeting({ setConferenceData, userInfo }: Props) { const { roomName, updateRoomName, submitRoomName } = useRoomName(); const [meetingStarted, setMeetingStarted] = useState(false); - const userInfo: UserInfo = { displayName: "unknown traveller", email: "" } - - const startMeeting = useCallback(() => { submitRoomName(); setMeetingStarted(true); }, [submitRoomName, setMeetingStarted]); if (meetingStarted) { - return ; + return ( + + ); } return ( diff --git a/frontend/src/components/sidebar/Sidebar.tsx b/frontend/src/components/sidebar/Sidebar.tsx index 1de2fae..7e2479d 100644 --- a/frontend/src/components/sidebar/Sidebar.tsx +++ b/frontend/src/components/sidebar/Sidebar.tsx @@ -1,14 +1,37 @@ import SidebarHeader from "./SidebarHeader"; import useSidebarVisibility from "./useSidebarVisibility"; import "./Sidebar.css"; +import { UsersData } from "../../background/types/roomData"; -function Sidebar() { +interface Props { + usersData: UsersData; +} + +function Sidebar(props: Props) { const { sidebarVisibility, toggleSidebarVisibility, sidebarToggleText } = useSidebarVisibility(); return (
+
+

No room

+ {props.usersData.usersWithOutRoom.map((username) => ( +
{username}
+ ))} +
+
+ {props.usersData.roomsData.map((roomData) => { + return ( + <> +

{roomData.roomName}

+ {roomData.participants.map((participant) => ( +
{participant.displayName}
+ ))} + + ); + })} +
diff --git a/frontend/src/hooks/useBackendData.ts b/frontend/src/hooks/useBackendData.ts index ffd0640..e348ca8 100644 --- a/frontend/src/hooks/useBackendData.ts +++ b/frontend/src/hooks/useBackendData.ts @@ -1,20 +1,26 @@ +import { useEffect } from "react"; +import { UserInfo } from "../components/jitsi/types"; +import useRoomData from "./useRoomData"; import useWebSocketConnection from "./useWebSocketConnection"; -function useBackendData() { +function useBackendData(userInfo: UserInfo) { + console.log("[Rooms] useBackendData"); + const { onMessage, sendMessage } = useWebSocketConnection(userInfo); - const { onMessage, sendMessage } = useWebSocketConnection(); + const { roomData, setRoomData } = useRoomData(); - const { roomData, setRoomData } = useRoomData() - - - onMessage( - (messageString) => { - const messageObject = JSON.parse(messageString) - - !!messageObject.roomData && setRoomData(messageObject.roomData) - - } - ) + useEffect( + () => + onMessage((messageString) => { + console.log("[Rooms] message from ws", messageString); + const messageObject = JSON.parse(messageString); + !!messageObject.roomsData && setRoomData(messageObject); + }), + [onMessage, setRoomData] + ); + return { roomData, sendMessage }; } + +export default useBackendData; diff --git a/frontend/src/hooks/useConferenceData.ts b/frontend/src/hooks/useConferenceData.ts new file mode 100644 index 0000000..aeb1cec --- /dev/null +++ b/frontend/src/hooks/useConferenceData.ts @@ -0,0 +1,21 @@ +import { useState } from "react"; +import { ConferenceData } from "../background/jitsi/eventListeners"; +import { UserInfo } from "../components/jitsi/types"; + +function useConferenceData( + sendMessage: (message: string) => void, + setUserInfo: (newData: UserInfo) => void +) { + const [conferenceData, setConferenceDataLocal] = useState(); + + const setConferenceData = (newData: ConferenceData) => { + console.log("[Rooms] set conferenceData"); + sendMessage(JSON.stringify(newData)); + setConferenceDataLocal(newData); + setUserInfo({ displayName: newData.displayName, email: "" }); + }; + + return { conferenceData, setConferenceData }; +} + +export default useConferenceData; diff --git a/frontend/src/hooks/useLocalUser.ts b/frontend/src/hooks/useLocalUser.ts new file mode 100644 index 0000000..63cb14d --- /dev/null +++ b/frontend/src/hooks/useLocalUser.ts @@ -0,0 +1,33 @@ +import { useState } from "react"; +import { USER_COOKIE_NAME } from "../background/constants"; +import { getCookie, setCookie } from "../background/cookies"; +import { UserInfo } from "../components/jitsi/types"; + +function useLocalUser() { + const [userInfo, setUserInfoLocal] = useState(() => + getUserInfoFromCookie() + ); + + const setUserInfo = (newData: UserInfo) => { + storeUserInfoInCookie(newData); + setUserInfoLocal(newData); + }; + + return { userInfo, setUserInfo }; +} + +function getUserInfoFromCookie(): UserInfo { + let cookie = getCookie(USER_COOKIE_NAME); + console.log("[Rooms] getUserNameFromCookie 1", cookie); + if (cookie) return JSON.parse(cookie); + return { + displayName: "unknown traveller", + email: "", + }; +} + +function storeUserInfoInCookie(userInfo: UserInfo) { + setCookie(USER_COOKIE_NAME, JSON.stringify(userInfo)); +} + +export default useLocalUser; diff --git a/frontend/src/hooks/useRoomData.ts b/frontend/src/hooks/useRoomData.ts index e69de29..2709b93 100644 --- a/frontend/src/hooks/useRoomData.ts +++ b/frontend/src/hooks/useRoomData.ts @@ -0,0 +1,10 @@ +import { useState } from "react"; +import { UsersData } from "../background/types/roomData"; + +function useRoomData() { + const [roomData, setRoomData] = useState(); + + return { roomData, setRoomData }; +} + +export default useRoomData; diff --git a/frontend/src/hooks/useRoomName.ts b/frontend/src/hooks/useRoomName.ts index 48f030a..2e352d9 100644 --- a/frontend/src/hooks/useRoomName.ts +++ b/frontend/src/hooks/useRoomName.ts @@ -1,7 +1,7 @@ import { useCallback, useState } from "react"; function useRoomName() { - const [roomName, setRoomName] = useState(getRoomNameFromUrl()); + const [roomName, setRoomName] = useState(() => getRoomNameFromUrl()); const updateRoomName = useCallback( (newName: string) => { @@ -11,13 +11,10 @@ function useRoomName() { [setRoomName] ); - const submitRoomName = useCallback( - () => { - setRoomNameInUrl(roomName); - setRoomNameInTitle(roomName); - }, - [roomName] - ); + const submitRoomName = useCallback(() => { + setRoomNameInUrl(roomName); + setRoomNameInTitle(roomName); + }, [roomName]); return { roomName, updateRoomName, submitRoomName }; } diff --git a/frontend/src/hooks/useWebSocketConnection.ts b/frontend/src/hooks/useWebSocketConnection.ts index 57f3811..6e13725 100644 --- a/frontend/src/hooks/useWebSocketConnection.ts +++ b/frontend/src/hooks/useWebSocketConnection.ts @@ -1,34 +1,46 @@ import { useCallback, useState } from "react"; import { WEBSOCKET_URL } from "../background/constants"; +import { UserInfo } from "../components/jitsi/types"; -function useWebSocketConnection() { +const createWebSocketConnection = (userInfo: UserInfo) => { + const webSocket = new WebSocket(WEBSOCKET_URL); + console.log("[Rooms] createWebSocketConnection"); + webSocket.addEventListener("open", (_: Event) => + webSocket.send(JSON.stringify(userInfo.displayName)) + ); - const createWebSocketConnection = () => { - const webSocket = new WebSocket(WEBSOCKET_URL); - return webSocket - } + return webSocket; +}; - const [webSocketConnection] = useState(createWebSocketConnection()) +function useWebSocketConnection(userInfo: UserInfo) { + console.log("[Rooms] useWebSocketConnection"); - const sendMessage = useCallback((message: string) => { - //if the socket's open, send a message: - if (webSocketConnection.readyState === WebSocket.OPEN) { - webSocketConnection.send(message); - } - }, + const [webSocketConnection] = useState(() => + createWebSocketConnection(userInfo) + ); + + const sendMessage = useCallback( + (message: string) => { + //if the socket's open, send a message: + if (webSocketConnection.readyState === WebSocket.OPEN) { + console.log("[Rooms] sending to ws", message); + webSocketConnection.send(message); + } + }, [webSocketConnection] - ) + ); const onMessage = useCallback( (messageHandler: (messageHandler: string) => void) => { - const wsMessageHandler = (ev: MessageEvent) => { messageHandler(ev.data) } - webSocketConnection.addEventListener('message', wsMessageHandler) + const wsMessageHandler = (ev: MessageEvent) => { + messageHandler(ev.data); + }; + webSocketConnection.addEventListener("message", wsMessageHandler); }, [webSocketConnection] - ) + ); - return { onMessage, sendMessage } + return { onMessage, sendMessage }; } export default useWebSocketConnection; - diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 43b95f8..08008e3 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -558,6 +558,11 @@ json5@^2.2.2: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +just-curry-it@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/just-curry-it/-/just-curry-it-5.3.0.tgz#1463602e932c5beb431a2a384dddcd48bb3c9c42" + integrity sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw== + loose-envify@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"