diff --git a/packages/legacy/Login/index.tsx b/packages/legacy/Login/index.tsx deleted file mode 100644 index b571d70..0000000 --- a/packages/legacy/Login/index.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import Channels, { STORE_ITEMS } from "../../electron/constants"; -import { useEffect, useState } from "react"; -import { useRecoilState, useSetRecoilState } from "recoil"; - -import { $jwtToken } from "../../controller/recoil"; -import { FcGoogle } from "react-icons/fc"; -import Logo from "../../components/Logo"; -import { useLogin } from "../../controller/index"; - -export default function Login() { - const { mutateAsync } = useLogin(); - const [isLoading, setIsLoading] = useState(false); - - const setJwtToken = useSetRecoilState($jwtToken); - - useEffect(() => { - window.electronAPI.once( - Channels.GOOGLE_AUTH_TOKENS, - async (tokens: { - access_token: string; - id_token: string; - refresh_token: string; - }) => { - console.log("got tokens", tokens); - - setIsLoading(true); - // todo: implement refresh token procedure in api layer by sending refresh_token and such - - const loginResponse = await mutateAsync({ - accessToken: tokens.access_token, - idToken: tokens.id_token, - }); - - const { jwtToken, userDetails } = loginResponse; - setJwtToken(jwtToken); - } - ); - - // todo: figure out how to clean up with the preload api - // return () => { - // window.electronAPI.removeAllListeners(Channels.GOOGLE_AUTH_TOKENS); - // }; - }, []); - - const continueAuth = () => { - setIsLoading(true); - - // send to main process - window.electronAPI.auth.initiateLogin(); - }; - - return ( -
- - - {/* ! TESTING PURPOSES */} -
- - - - - -
- - {isLoading ? ( - <> - Attempting to log you in - - ) : ( - - )} -
- ); -} diff --git a/packages/legacy/components/Logo/index.tsx b/packages/legacy/components/Logo/index.tsx deleted file mode 100644 index 8cd36ee..0000000 --- a/packages/legacy/components/Logo/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -export type LogoType = "primary" | "small"; - -export default function Logo({ - className, - type, - grayscale = false, -}: { - className?: string; - type?: LogoType; - grayscale?: boolean; -}) { - if (type === "small") { - return ( - - - - - - ); - } - return ( - - - - - - - - - - - - - - ); -} diff --git a/packages/legacy/components/ProtectedRoute/index.tsx b/packages/legacy/components/ProtectedRoute/index.tsx deleted file mode 100644 index 95daf04..0000000 --- a/packages/legacy/components/ProtectedRoute/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useAuthCheck, useServerCheck } from "../../controller/index"; - -import { $jwtToken } from "../../controller/recoil"; -import Login from "../../pages/Login"; -import NirvanaApi from "../../controller/nirvanaApi"; -import { STORE_ITEMS } from "../../electron/constants"; -import SkeletonLoader from "../loading/skeleton"; -import { useEffect } from "react"; -import { useRecoilState } from "recoil"; - -export default function ProtectedRoute({ - children, -}: { - children?: React.ReactNode; -}) { - const [jwtToken, setJwtToken] = useRecoilState($jwtToken); - - const { - isError: serverFailure, - isLoading: serverLoading, - isSuccess: isServerHealthy, - error: serverStatusError, - isFetching: serverFetching, - } = useServerCheck(); - - const { isLoading, isError, isFetching, isSuccess, refetch } = useAuthCheck( - !serverLoading - ); - - // ! REMOVING FOR TESTING MULTIPLE CLIENTS - // useEffect(() => { - // // on load of this, if we already have jwt tokens in store, - // // then try using them with auth check - // window.electronAPI.store - // .get(STORE_ITEMS.AUTH_SESSION_JWT) - // .then((jwtToken: string) => { - // if (jwtToken) { - // NirvanaApi._jwtToken = jwtToken; - // setJwtToken(jwtToken); - // refetch(); - - // console.log("retrieved jwtToken from storage", jwtToken); - // } else { - // console.log("no jwt token in store"); - // } - // }); - // }, []); - - useEffect(() => { - console.log("change in jwt token", jwtToken); - - if (jwtToken) { - window.electronAPI.store.set(STORE_ITEMS.AUTH_SESSION_JWT, jwtToken); - setJwtToken(jwtToken); - } else { - window.electronAPI.store.set(STORE_ITEMS.AUTH_SESSION_JWT, null); - } - - NirvanaApi._jwtToken = jwtToken; - - refetch(); - }, [jwtToken]); - - // first time loading - if (serverLoading) - return ( - - Sorry...this is our bad. Our servers are loading. We are trying our best - to back up and running! :)
Please contact me for urgent concerns: - arjunpatel@berkeley.edu -
- ); - - if (isLoading) { - return ( -
- -
- ); - } - - if (isError) { - return ; - } - - // if we can successfully get user details, we are good to continue - // basic auth...any additional auth should be done at lower levels - if (isSuccess) { - return <>{children}; - } - - return ; -} diff --git a/packages/legacy/components/User/avatarWithBadge.tsx b/packages/legacy/components/User/avatarWithBadge.tsx deleted file mode 100644 index b8535a4..0000000 --- a/packages/legacy/components/User/avatarWithBadge.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Avatar, Badge } from "antd"; - -import { UserOutlined } from "@ant-design/icons"; - -const AvatarWithBadge = ({ src }: { src: string }) => ( - <> - - - - - - -); - -export default AvatarWithBadge; diff --git a/packages/legacy/components/User/basicUserDetailsRow.tsx b/packages/legacy/components/User/basicUserDetailsRow.tsx deleted file mode 100644 index 802b9f8..0000000 --- a/packages/legacy/components/User/basicUserDetailsRow.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Avatar, Tooltip } from "antd"; - -import { FaAngleRight } from "react-icons/fa"; -import { ReactElement } from "react"; -import { User } from "@nirvana/core/models/user.model"; - -export default function BasicUserRow({ - user, - rightJsx, -}: { - user: User; - rightJsx?: ReactElement; -}) { - return ( - - - - - {user.name} - - {user.email} - - -
{rightJsx}
-
- ); -} diff --git a/packages/legacy/components/User/userAvatarWithStatus.tsx b/packages/legacy/components/User/userAvatarWithStatus.tsx deleted file mode 100644 index 63eaefc..0000000 --- a/packages/legacy/components/User/userAvatarWithStatus.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { User, UserStatus } from "@nirvana/core/models"; - -export default function UserAvatarWithStatus(props: { user: User }) { - return ( -
- - - -
- ); -} - -export function UserStatusBubble(props: { status: UserStatus }) { - switch (props.status) { - case UserStatus.ONLINE: - return ( - - ); - case UserStatus.OFFLINE: - return ( - - ); - case UserStatus.FLOW_STATE: - return ( - - ); - default: - return ( - - ); - } -} diff --git a/packages/legacy/components/User/userStatusText.tsx b/packages/legacy/components/User/userStatusText.tsx deleted file mode 100644 index 571957e..0000000 --- a/packages/legacy/components/User/userStatusText.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { UserStatus } from "@nirvana/core/models"; - -export default function UserStatusText(props: { status: UserStatus }) { - switch (props.status) { - case UserStatus.ONLINE: - return ONLINE; - case UserStatus.OFFLINE: - return <>; - case UserStatus.FLOW_STATE: - return ( - FLOW STATE - ); - default: - return <>; - } -} diff --git a/packages/legacy/components/header/index.tsx b/packages/legacy/components/header/index.tsx deleted file mode 100644 index 4467855..0000000 --- a/packages/legacy/components/header/index.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import { - $desktopMode, - $jwtToken, - $mediaSettings, -} from "../../controller/recoil"; -import { Avatar, Dropdown, Menu, Tooltip } from "antd"; -import { FiCheck, FiLogOut, FiMic, FiMicOff } from "react-icons/fi"; -import { GlobalHotKeys, KeyMap } from "react-hotkeys"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; - -import { FaSearch } from "react-icons/fa"; -import HorizontalLogo from "../../../../components/logo/horizontal"; -import Logo from "../Logo"; -import { LogoType } from "../../../../components/logo/full"; -import { useGetUserDetails } from "../../controller/index"; - -/** - * TODO: add in video mode - * - */ -export default function NirvanaHeader({ - onHeaderFocus, -}: { - onHeaderFocus: () => void; -}) { - const { data: userDetailsRes, isLoading, isError } = useGetUserDetails(); - - const inputRef = useRef(null); - const [searchInput, setSearchInput] = useState(""); - - const [desktopMode, setDesktopMode] = useRecoilState($desktopMode); - - const [mediaSettings, setMediaSettings] = useRecoilState($mediaSettings); - - const setJwtToken = useSetRecoilState($jwtToken); - - useEffect(() => { - if (isError) setJwtToken(null); - }, [isError]); - - /** hide the search bar in the header so that it's cleaner for these two modes */ - const shouldHideSearch = useMemo(() => { - if (desktopMode === "flowState" || desktopMode === "overlayOnly") - return true; - - return false; - }, [desktopMode]); - - const selectSearch = () => { - inputRef.current?.focus(); - }; - - const onSearchChange = (e) => { - setSearchInput(e.target.value); - }; - - const keyMap: KeyMap = { - SELECT_SEARCH: { - name: "select search to start searching", - sequence: "/", - action: "keyup", - }, - }; - const handlers = { - SELECT_SEARCH: selectSearch, - }; - - const handleMuteToggle = useCallback(() => { - setMediaSettings((previousMediaSettings) => ({ - ...previousMediaSettings, - isMuted: !previousMediaSettings.isMuted, - })); - }, [setMediaSettings]); - - const handleSignOut = useCallback(() => { - setJwtToken(null); - }, [setJwtToken]); - - if (isLoading) return <>loading; - - // todo: do I need a mute mode? isn't that just flow state - // might confuse user overall - const profileMenu = ( - - // {mediaSettings.isMuted ? "Unmute" : "Mute"} - // - // ), - // icon: <> {mediaSettings.isMuted ? : } , - // key: `profile-menu-${1}`, - // }, - { - label: Audio Only, - icon: <> {mediaSettings.mode === "audio" ? : <>} , - disabled: false, - key: `profile-menu-${2}`, - }, - { - label: ( - - Video - - ), - icon: <>{mediaSettings.mode === "video" ? : <>} , - disabled: true, - key: `profile-menu-${3}`, - }, - { - label: ( - - Screen - - ), - icon: <>{mediaSettings.mode === "screen" ? : <>} , - disabled: true, - key: `profile-menu-${3}`, - }, - - { - type: "divider", - key: `profile-menu-${4}`, - }, - { - label: Sign Out, - icon: , - key: `profile-menu-${5}`, - danger: true, - }, - ]} - /> - ); - - return ( - <> - - -
- -
- -
-
- - {!shouldHideSearch && ( -
- - -
- )} - - {/* todo: move this ghost button to components */} - {desktopMode === "flowState" ? ( - - ) : ( - - )} - - -
- {userDetailsRes?.user?.picture && - mediaSettings.mode === "audio" && ( - - )} -
-
- - {/* menu for the output options */} - {/* - setOutputMode("audio")}> - - - - Audio Only - - - setOutputMode("video")}> - - - - Video - - */} -
- - ); -} diff --git a/packages/legacy/components/lines/lineIcon/index.tsx b/packages/legacy/components/lines/lineIcon/index.tsx deleted file mode 100644 index 517bd98..0000000 --- a/packages/legacy/components/lines/lineIcon/index.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Avatar } from "antd"; -import React from "react"; -import { useMemo } from "react"; - -function LineIcon({ - sourceImages, - grayscale = true, -}: { - sourceImages: string[]; - grayscale: boolean; -}) { - const isMultiple = useMemo(() => { - if (sourceImages?.length > 1) return true; - - return false; - }, [sourceImages]); - - return ( - <> - {isMultiple ? ( -
- {sourceImages.map((avatarSrc, index) => { - if (index === 0) { - return ( - - ); - } else if (index === 1) { - return ( - - ); - } - - // TODO: add third in the frame if we want later - return <>; - })} -
- ) : ( - sourceImages?.map((avatarSrc, index) => ( - - )) - )} - - ); -} - -export default React.memo(LineIcon); diff --git a/packages/legacy/components/lines/lineRow.tsx/index.tsx b/packages/legacy/components/lines/lineRow.tsx/index.tsx deleted file mode 100644 index c01838b..0000000 --- a/packages/legacy/components/lines/lineRow.tsx/index.tsx +++ /dev/null @@ -1,424 +0,0 @@ -import { FiActivity, FiSun } from 'react-icons/fi'; -import { - RtcAnswerRequest, - RtcCallRequest, - RtcNewUserResponse, - RtcReceiveAnswerResponse, - ServerResponseChannels, - SomeoneUntunedFromLineResponse, -} from '@nirvana/core/sockets/channels'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; - -import { Avatar } from 'antd'; -import LineIcon from '../lineIcon'; -import { LineMemberState } from '@nirvana/core/models/line.model'; -import MasterLineData from '@nirvana/core/models/masterLineData.model'; -import Peer from 'simple-peer'; -import { ServerRequestChannels } from '@nirvana/core/sockets/channels'; -import moment from 'moment'; -import toast from 'react-hot-toast'; -import { useRecoilState } from 'recoil'; -import useAuth from '../../../../desktop/src/providers/AuthProvider'; - -// todo: send a much more comprehensive master line object? or just add properties to the -// masterLineData object so that we don't have different models to maintain between client and server - -export default function LineRow({ - masterLineData, - handleSelectLine, -}: { - masterLineData: MasterLineData; - handleSelectLine: (lineId: string) => void; -}) { - const [selectedLineId, setSelectedLineId] = useRecoilState($selectedLineId); - const { user } = useAuth(); - - useEffect(() => { - console.warn('mounting linerow'); - - return () => console.warn('UNMOUNTING line row'); - }, []); - - // take the source of truth list of memeberIds tuned in, and see if I'm in it - const isUserTunedIn = useMemo( - () => masterLineData.tunedInMemberIds?.includes(userData?.user?._id.toString()), - [masterLineData.tunedInMemberIds, userData], - ); - - /** - * TODO: slowly add to this and fix based on added features - * */ - const renderActivityIcon = useMemo(() => { - // if there is someone or me broadcasting here - if (masterLineData.currentBroadcastersUserIds?.length > 0) - return ; - - if (isUserTunedIn) return ; - - // if there is new activity blocks for me - if (masterLineData.currentUserMember.lastVisitDate) - return ; - - return ; - }, [masterLineData, isUserTunedIn]); - - const renderRightActivity = useMemo(() => { - // TODO: get the profile pictures of the broadcasters - if (masterLineData.currentBroadcastersUserIds?.length > 0) - return ( - - {masterLineData.otherUserObjects?.map((otherUser, index) => ( - - ))} - - ); - - // if there is new activity/black dot, then show relative time as little bolder? or too much? - - // TODO: compare last visit date to latest audio block - if (false) - return ( - - {moment(masterLineData.currentUserMember.lastVisitDate).fromNow(true)} - - ); - - return ( - - {moment(masterLineData.currentUserMember.lastVisitDate).fromNow(true)} - - ); - }, [masterLineData]); - - const profilePictures = useMemo(() => { - const pictureSources: string[] = []; - - // ?don't add in my image as that's useless contextually? - // if (userData?.user?.picture) pictureSources.push(userData.user.picture); - - masterLineData.otherUserObjects?.forEach((otherUser) => { - if (otherUser.picture) pictureSources.push(otherUser.picture); - }); - - return pictureSources; - }, [masterLineData, userData]); - - return ( - <> -
handleSelectLine(masterLineData.lineDetails._id.toString())} - className={`flex flex-row items-center justify-start gap-2 p-2 px-4 h-14 hover:bg-gray-200 cursor-pointer transition-all - last:border-b-0 border-b border-b-gray-200 relative z-50 rounded ${ - selectedLineId === masterLineData.lineDetails._id.toString() && - 'bg-gray-200 scale-110 shadow-2xl translate-x-3' - }`} - > - {/* status dot */} -
{renderActivityIcon}
- - {profilePictures && } - -

- {masterLineData.lineDetails.name || masterLineData.otherUserObjects[0].givenName} -

- -
{renderRightActivity}
-
- - {/* mounts and unmounts based on if in the room or now */} - {/* {isUserTunedIn && ( - - )} */} - - ); -} - -type PeerMap = { [userId: string]: Peer }; - -function StreamRoom({ - lineId, - tunedInUsers, - currentBroadcasters, -}: { - lineId: string; - tunedInUsers?: string[]; - currentBroadcasters?: string[]; -}) { - // ?could move this up the tree and pass it down? or set it in the header? - // local stream specifically for this stream room - const [localStream, setLocalStream] = useState(); - const userStreamTagRef = useRef(null); - const { data: userDetails } = useGetUserDetails(); - - // local peer map of userIds to peers - const [userPeers, setUserPeers] = useState({}); - - // ws listen to events of user disconnecting and such or rely on tunedin members prop - const { $ws } = useLineDataProvider(); - - // todo: get the right constraints based on user settings...audio, video, or none? header selections? make a decision - useEffect(() => { - if (userDetails) { - navigator.mediaDevices - .getUserMedia({ - video: false, - audio: { - echoCancellation: true, - autoGainControl: true, - }, - }) - .then((userStream) => { - setLocalStream(userStream); - - if (userStreamTagRef?.current) userStreamTagRef.current.srcObject = userStream; - - // alternative to dom element - // const audio = new Audio(); - // audio.autoplay = true; - // audio.srcObject = userStream; - - // !TEST STUFF : removing distortion with headphones in - // test distortion to go away on disabling audio - // setTimeout(() => { - // console.log("stopping audio stream "); - - // userStream.getTracks().forEach((track) => { - // track.enabled = !track.enabled; - - // track.stop(); - // }); - // }, 0); - - // take the initial list of tunedInUsers without my own id - const everyOtherTunedUserId = tunedInUsers.filter( - (tunedUserId) => tunedUserId !== userDetails?.user._id.toString(), - ); - - const localPeerConnections: PeerMap = {}; - - everyOtherTunedUserId.forEach((otherTunedInUserId) => { - // create local peer objects for them - const localPeerInitiator = new Peer({ - initiator: true, - stream: userStream, - trickle: false, // prevents the multiple tries on different ice servers and signal from getting called a bunch of times - }); - - // set it for our map that we will iterate through to display streams in other child view - localPeerConnections[otherTunedInUserId] = localPeerInitiator; - - // notify each one with specific signal - localPeerInitiator.on('signal', (signal) => { - $ws.emit( - ServerRequestChannels.RTC_CALL_REQUEST, - new RtcCallRequest(lineId, otherTunedInUserId, signal), - ); - }); - }); - - setUserPeers(localPeerConnections); - - // answer calls - $ws.on( - `${ServerResponseChannels.RTC_NEW_USER_JOINED_RESPONSE_PREFIX}:${lineId}`, - (res: RtcNewUserResponse) => { - // create a local peer connection for this new user - - console.log( - 'ooo newbie joined room, I guess I will accept it and send him my signal', - ); - console.log(res); - - var peerForMeAndNewbie = new Peer({ - initiator: false, - trickle: false, // prevents the multiple tries on different ice servers and signal from getting called a bunch of times - stream: userStream, // add in my own stream that I got before - }); - - peerForMeAndNewbie.on('signal', (signal) => { - console.log( - 'as the answerer, I am going to send back my signal so that the newbie can update his local peer for me', - ); - $ws.emit( - ServerRequestChannels.RTC_ANSWER_REQUEST, - new RtcAnswerRequest(lineId, res.newUserId, signal), - ); - }); - - peerForMeAndNewbie.signal(res.simplePeerSignal); - - setUserPeers((prevUsersPeers) => { - const newUserPeers = { ...prevUsersPeers }; - - newUserPeers[res.newUserId] = peerForMeAndNewbie; - - return newUserPeers; - }); - }, - ); - - // take care of answers recevied - $ws.on( - `${ServerResponseChannels.RTC_RECEIVING_ANSWER_RESPONSE_PREFIX}:${lineId}`, - (res: RtcReceiveAnswerResponse) => { - console.log( - `oooo some master received my call and accepted it ${JSON.stringify(res)}`, - ); - console.log(res); - - // find the peer we created earlier for this master - // ?is this okay? using the setter to get the current state? - setUserPeers((previousUserPeersMap) => { - const newUserPeerMap = { ...previousUserPeersMap }; - - console.log('here is the current peers map', previousUserPeersMap); - - const peerForAnswerer = newUserPeerMap[res.answererUserId]; - - if (peerForAnswerer) { - peerForAnswerer.signal(res.simplePeerSignal); - } else { - console.error('could not find the peer we created before for this master'); - } - - // note needed to change state so that the peer object that gets iterated in dom isn't the old referenced one/we trigger refresh for the child component - return newUserPeerMap; - }); - }, - ); - - $ws.on( - `${ServerResponseChannels.SOMEONE_UNTUNED_FROM_LINE}:${lineId}`, - (res: SomeoneUntunedFromLineResponse) => { - // find the peer object and remove from our userPeers map - // will also cause unmounting the child component which destroys peer object but also can destroy here - }, - ); - }) - .catch((error) => { - console.error(error); - - toast.error('Make sure that you have permissions enabled and microphone connected'); - }); - } - - // TODO: p1 : destroy peers on load - // ! remove ws handlers so that the same channels don't get triggered twice when I retune into this line - return () => { - $ws.removeListener( - `${ServerResponseChannels.RTC_RECEIVING_ANSWER_RESPONSE_PREFIX}:${lineId}`, - ); - $ws.removeListener(`${ServerResponseChannels.RTC_NEW_USER_JOINED_RESPONSE_PREFIX}:${lineId}`); - }; - }, [userDetails, setUserPeers]); - - // calculate diff to clean our userPeerMap to unmount and detroy certain peer connections - useEffect(() => {}, [tunedInUsers]); - - useEffect(() => { - console.log('keeping an eye on user peers map'); - console.log(userPeers); - }, [userPeers]); - - // todo, someone tell the main object that I am finally connected after everything...different than tuned in - - // TODO: p1...when the peer map user count > tunedIn.length, then we get rid of the right person from list cuzz they have officially left or disconnected - useEffect(() => { - console.log('change in tuned in users in the streaming room!!!'); - - setUserPeers((prevUserPeersMap) => { - // go through the userIds here - // if tunedIn users doesn't have a userId, this guy prolly disconnected - - const newMap = { ...prevUserPeersMap }; //ensures going through the list of peers again to remove specific ones - - const otherUserIdsPeers = Object.keys(prevUserPeersMap); - - otherUserIdsPeers.forEach((otherUserId) => { - // problem if we are trying to show stream of someone who is not tuned in - if (!tunedInUsers.includes(otherUserId)) { - console.log('user left with id:', otherUserId); - const disconnectedLocalPeer = newMap[otherUserId]; - - if (disconnectedLocalPeer) { - disconnectedLocalPeer.peer?.destroy(); - delete newMap[otherUserId]; - } - } - }); - - return newMap; - }); - }, [tunedInUsers]); - - // todo: when user isUserBroadcasting is false, disable localUserStream in this component - // and also emit, which should already have been done - - // we only loop through peers that are associated to user Ids which exist in the currentBroadcasters array - return ( - <> -