joining and leaving backend side done and now agora test here we go
This commit is contained in:
@@ -13,7 +13,7 @@ import AgoraRTC, {
|
|||||||
import { getFunctions, httpsCallable } from "firebase/functions";
|
import { getFunctions, httpsCallable } from "firebase/functions";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
const config: ClientConfig = {
|
export const config: ClientConfig = {
|
||||||
mode: "rtc",
|
mode: "rtc",
|
||||||
codec: "vp8",
|
codec: "vp8",
|
||||||
};
|
};
|
||||||
@@ -22,7 +22,7 @@ const config: ClientConfig = {
|
|||||||
// const useMicrophoneTracks = createMicrophoneAudioTrack();
|
// const useMicrophoneTracks = createMicrophoneAudioTrack();
|
||||||
|
|
||||||
// TODO: move this to env file
|
// TODO: move this to env file
|
||||||
const appId: string = "c8dfd65deb5c4741bd564085627139d0"; //ENTER APP ID HERE
|
export const appId: string = "c8dfd65deb5c4741bd564085627139d0"; //ENTER APP ID HERE
|
||||||
|
|
||||||
export default class AgoraService {
|
export default class AgoraService {
|
||||||
private functions = getFunctions();
|
private functions = getFunctions();
|
||||||
@@ -90,5 +90,3 @@ export default class AgoraService {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
export { appId };
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
setDoc,
|
setDoc,
|
||||||
runTransaction,
|
runTransaction,
|
||||||
writeBatch,
|
writeBatch,
|
||||||
|
arrayUnion,
|
||||||
|
arrayRemove,
|
||||||
} from "firebase/firestore";
|
} from "firebase/firestore";
|
||||||
import Conversation, {
|
import Conversation, {
|
||||||
AudioClip,
|
AudioClip,
|
||||||
@@ -179,7 +181,33 @@ export default class ConversationService {
|
|||||||
},
|
},
|
||||||
{ merge: true }
|
{ merge: true }
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
async joinLiveConversation(convoId: string, userJoiningId: string) {
|
||||||
|
// todo: make sure that user is part of the convo or valid in it
|
||||||
|
const docRef = doc(this.db, Collections.conversations, convoId);
|
||||||
|
await setDoc(
|
||||||
|
docRef,
|
||||||
|
{
|
||||||
|
membersInLiveRoom: arrayUnion(userJoiningId),
|
||||||
|
lastActivityDate: serverTimestamp(),
|
||||||
|
lastUpdatedBy: userJoiningId,
|
||||||
|
},
|
||||||
|
{ merge: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async leaveLiveConversation(convoId: string, userJoiningId: string) {
|
||||||
|
// todo: make sure that user is part of the convo or valid in it
|
||||||
|
const docRef = doc(this.db, Collections.conversations, convoId);
|
||||||
|
await setDoc(
|
||||||
|
docRef,
|
||||||
|
{
|
||||||
|
membersInLiveRoom: arrayRemove(userJoiningId),
|
||||||
|
lastActivityDate: serverTimestamp(),
|
||||||
|
lastUpdatedBy: userJoiningId,
|
||||||
|
},
|
||||||
|
{ merge: true }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import AgoraService from "./agoraService";
|
||||||
import CloudStorageService from "./cloudStorageService";
|
import CloudStorageService from "./cloudStorageService";
|
||||||
import ConversationService from "./conversationService";
|
import ConversationService from "./conversationService";
|
||||||
import UserService from "./userService";
|
import UserService from "./userService";
|
||||||
@@ -7,3 +8,5 @@ export const conversationService = new ConversationService();
|
|||||||
export const userService = new UserService();
|
export const userService = new UserService();
|
||||||
|
|
||||||
export const cloudStorageService = new CloudStorageService();
|
export const cloudStorageService = new CloudStorageService();
|
||||||
|
|
||||||
|
export const agoraService = new AgoraService();
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import { Routes } from "@nirvana/common/helpers/routes";
|
|||||||
|
|
||||||
export default function ConversationFullRow(props: {
|
export default function ConversationFullRow(props: {
|
||||||
conversation: Conversation;
|
conversation: Conversation;
|
||||||
|
handleJoinLive: (conversationId: string) => Promise<void>;
|
||||||
|
handleLeaveLive: (conversationId: string) => Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
const { currUser } = useAuth();
|
const { currUser } = useAuth();
|
||||||
const usersConvosMap = useRecoilValue(allUsersConversationsAtom);
|
const usersConvosMap = useRecoilValue(allUsersConversationsAtom);
|
||||||
@@ -75,10 +77,6 @@ export default function ConversationFullRow(props: {
|
|||||||
audio.play();
|
audio.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
const joinLive = () => {
|
|
||||||
toast("connecting");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show actions based on which state it is currently in for this user
|
* show actions based on which state it is currently in for this user
|
||||||
*/
|
*/
|
||||||
@@ -134,7 +132,7 @@ export default function ConversationFullRow(props: {
|
|||||||
<span
|
<span
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
joinLive();
|
props.handleJoinLive(props.conversation.id);
|
||||||
}}
|
}}
|
||||||
className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200"
|
className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200"
|
||||||
>
|
>
|
||||||
@@ -164,7 +162,7 @@ export default function ConversationFullRow(props: {
|
|||||||
isNewIncoming ? "text-slate-400 font-semibold" : "text-slate-300"
|
isNewIncoming ? "text-slate-400 font-semibold" : "text-slate-300"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{moment(props.conversation.lastActivityDate.toDate()).fromNow()}
|
{moment(props.conversation.lastActivityDate?.toDate()).fromNow()}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* actions */}
|
{/* actions */}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import Conversation from "@nirvana/common/models/conversation";
|
import Conversation from "@nirvana/common/models/conversation";
|
||||||
|
import { agoraService, conversationService } from "@nirvana/common/services";
|
||||||
import { Avatar, Tooltip } from "antd";
|
import { Avatar, Tooltip } from "antd";
|
||||||
import { FaWalking } from "react-icons/fa";
|
import toast from "react-hot-toast";
|
||||||
|
import { FaPhoneSlash, FaWalking } from "react-icons/fa";
|
||||||
|
import { useAuth } from "../../contexts/authContext";
|
||||||
import { MasterAvatarGroupWithUserFetch } from "../UserDetails/MasterAvatarGroup";
|
import { MasterAvatarGroupWithUserFetch } from "../UserDetails/MasterAvatarGroup";
|
||||||
|
|
||||||
export default function LiveRoom(props: { conversation: Conversation }) {
|
export default function LiveRoom(props: {
|
||||||
|
conversation: Conversation;
|
||||||
|
handleJoinLive: (conversationId: string) => Promise<void>;
|
||||||
|
handleLeaveLive: (conversationId: string) => Promise<void>;
|
||||||
|
}) {
|
||||||
|
const { currUser } = useAuth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="group relative flex flex-row items-center bg-slate-50 rounded-lg border p-5 animate-pulse">
|
<span className="group relative flex flex-row items-center bg-slate-50 rounded-lg border p-5 animate-pulse">
|
||||||
<MasterAvatarGroupWithUserFetch
|
<MasterAvatarGroupWithUserFetch
|
||||||
@@ -19,14 +28,27 @@ export default function LiveRoom(props: { conversation: Conversation }) {
|
|||||||
{"01:20"}
|
{"01:20"}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Tooltip title={"Join live conversation"}>
|
{props.conversation.membersInLiveRoom.includes(currUser!.uid) ? (
|
||||||
<span
|
<Tooltip title={"Leave"}>
|
||||||
className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200
|
<span
|
||||||
|
onClick={() => props.handleLeaveLive(props.conversation.id)}
|
||||||
|
className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200
|
||||||
|
absolute right-5 text-slate-50 group-hover:text-red-600 text-lg transition-all -z-10 group-hover:z-10"
|
||||||
|
>
|
||||||
|
<FaPhoneSlash className="text-lg" />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Tooltip title={"Join live conversation"}>
|
||||||
|
<span
|
||||||
|
onClick={() => props.handleJoinLive(props.conversation.id)}
|
||||||
|
className="p-2 rounded-full hover:cursor-pointer hover:bg-slate-200
|
||||||
absolute right-5 text-slate-50 group-hover:text-teal-600 text-lg transition-all -z-10 group-hover:z-10"
|
absolute right-5 text-slate-50 group-hover:text-teal-600 text-lg transition-all -z-10 group-hover:z-10"
|
||||||
>
|
>
|
||||||
<FaWalking className="text-lg" />
|
<FaWalking className="text-lg" />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export default function LiveRoomsHandler() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
@@ -24,11 +24,139 @@ import {
|
|||||||
RelativeTimeSeparatedConvosSelector,
|
RelativeTimeSeparatedConvosSelector,
|
||||||
} from "../../recoil/main";
|
} from "../../recoil/main";
|
||||||
import ConversationFullRow from "../Conversations/ConversationFullRow";
|
import ConversationFullRow from "../Conversations/ConversationFullRow";
|
||||||
|
import LiveRooms from "../LiveRooms/LiveRoomsHandler";
|
||||||
|
import LiveRoomsHandler from "../LiveRooms/LiveRoomsHandler";
|
||||||
|
import {
|
||||||
|
ClientConfig,
|
||||||
|
IAgoraRTC,
|
||||||
|
IAgoraRTCClient,
|
||||||
|
IMicrophoneAudioTrack,
|
||||||
|
} from "agora-rtc-sdk-ng";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { config, appId } from "@nirvana/common/services/agoraService";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import { agoraService, conversationService } from "@nirvana/common/services";
|
||||||
|
import { useAuth } from "../../contexts/authContext";
|
||||||
|
|
||||||
export default function Conversations() {
|
export default function Conversations() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { currUser } = useAuth();
|
||||||
|
|
||||||
const liveRooms = useRecoilValue(liveRoomsSelector);
|
const liveRooms = useRecoilValue(liveRoomsSelector);
|
||||||
|
|
||||||
|
const [agoraRtc, setAgoraRtc] = useState<IAgoraRTC>();
|
||||||
|
const [agoraRtcClient, setAgoraRtcClient] = useState<IAgoraRTCClient>();
|
||||||
|
const [localAudioTrack, setLocalAudioTrack] =
|
||||||
|
useState<IMicrophoneAudioTrack>();
|
||||||
|
|
||||||
|
// set up agora stuff
|
||||||
|
useEffect(() => {
|
||||||
|
(async function () {
|
||||||
|
// dynamic import as the server side import doesn't work
|
||||||
|
const AgoraRTC = (await import("agora-rtc-sdk-ng")).default;
|
||||||
|
const agoraClient = AgoraRTC.createClient(config);
|
||||||
|
|
||||||
|
setAgoraRtcClient(agoraClient);
|
||||||
|
setAgoraRtc(AgoraRTC);
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function handleJoinChannel(channelName: string, agoraToken: string) {
|
||||||
|
const localTrack: IMicrophoneAudioTrack =
|
||||||
|
await agoraRtc!.createMicrophoneAudioTrack();
|
||||||
|
|
||||||
|
setLocalAudioTrack(localTrack);
|
||||||
|
|
||||||
|
const init = async (chanName: string) => {
|
||||||
|
agoraRtcClient!.on("user-published", async (user, mediaType) => {
|
||||||
|
await agoraRtcClient!.subscribe(user, mediaType);
|
||||||
|
console.log("subscribe success");
|
||||||
|
|
||||||
|
if (mediaType === "audio") {
|
||||||
|
user.audioTrack?.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
agoraRtcClient!.on("user-unpublished", async (user, type) => {
|
||||||
|
console.log("unpublished", user, type);
|
||||||
|
if (type === "audio") {
|
||||||
|
user.audioTrack?.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
await agoraRtcClient!.unsubscribe(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
agoraRtcClient!.on("user-left", (user) => {
|
||||||
|
console.log("user left", user);
|
||||||
|
});
|
||||||
|
|
||||||
|
await agoraRtcClient!.join(appId, chanName, agoraToken, null);
|
||||||
|
if (localTrack) await agoraRtcClient!.publish(localTrack);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (localTrack) {
|
||||||
|
console.log("init ready");
|
||||||
|
init(channelName);
|
||||||
|
} else {
|
||||||
|
toast.error("Not ready for joining call");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLeaveChannel() {
|
||||||
|
// destroy local track
|
||||||
|
localAudioTrack?.close();
|
||||||
|
|
||||||
|
// leave all channels
|
||||||
|
await agoraRtcClient!.leave();
|
||||||
|
}
|
||||||
|
|
||||||
|
// click on join
|
||||||
|
// add me to the active list of people in the call
|
||||||
|
// update the conversation lastActivityDate as well
|
||||||
|
|
||||||
|
const handleJoinLive = async (conversationId: string) => {
|
||||||
|
const connectingToast = toast.loading("connecting");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// agora token from CF
|
||||||
|
const agoraToken = await agoraService.getAgoraToken(conversationId);
|
||||||
|
|
||||||
|
console.log(agoraToken);
|
||||||
|
|
||||||
|
await conversationService.joinLiveConversation(
|
||||||
|
conversationId,
|
||||||
|
currUser!.uid
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
toast.error("problem in joining");
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.remove(connectingToast);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLeaveLive = async (conversationId: string) => {
|
||||||
|
const leavingToast = toast.loading("leaving");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await conversationService.leaveLiveConversation(
|
||||||
|
conversationId,
|
||||||
|
currUser!.uid
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
toast.error("problem in joining");
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.remove(leavingToast);
|
||||||
|
};
|
||||||
|
|
||||||
|
// agora start the call stream
|
||||||
|
|
||||||
|
// leave the call on before unload if in a call
|
||||||
|
|
||||||
|
// update the database to remove my userId from the list of people in the room
|
||||||
|
|
||||||
const relativeConvoSections = useRecoilValue(
|
const relativeConvoSections = useRecoilValue(
|
||||||
RelativeTimeSeparatedConvosSelector
|
RelativeTimeSeparatedConvosSelector
|
||||||
);
|
);
|
||||||
@@ -68,7 +196,7 @@ export default function Conversations() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* live */}
|
{/* live rooms */}
|
||||||
{liveRooms?.length > 0 && (
|
{liveRooms?.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="flex flex-row items-center mb-5 px-5 w-full">
|
<span className="flex flex-row items-center mb-5 px-5 w-full">
|
||||||
@@ -81,7 +209,12 @@ export default function Conversations() {
|
|||||||
{/* row of live room cards */}
|
{/* row of live room cards */}
|
||||||
<span className="flex flex-col w-full items-stretch">
|
<span className="flex flex-col w-full items-stretch">
|
||||||
{liveRooms.map((lRoom) => (
|
{liveRooms.map((lRoom) => (
|
||||||
<LiveRoom key={lRoom.id} conversation={lRoom} />
|
<LiveRoom
|
||||||
|
key={lRoom.id}
|
||||||
|
conversation={lRoom}
|
||||||
|
handleJoinLive={handleJoinLive}
|
||||||
|
handleLeaveLive={handleLeaveLive}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -97,7 +230,12 @@ export default function Conversations() {
|
|||||||
</span>
|
</span>
|
||||||
<span className="flex flex-col w-full items-stretch">
|
<span className="flex flex-col w-full items-stretch">
|
||||||
{todayConvos.map((tRoom) => (
|
{todayConvos.map((tRoom) => (
|
||||||
<ConversationFullRow key={tRoom.id} conversation={tRoom} />
|
<ConversationFullRow
|
||||||
|
key={tRoom.id}
|
||||||
|
conversation={tRoom}
|
||||||
|
handleJoinLive={handleJoinLive}
|
||||||
|
handleLeaveLive={handleLeaveLive}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -113,7 +251,12 @@ export default function Conversations() {
|
|||||||
</span>
|
</span>
|
||||||
<span className="flex flex-col w-full items-stretch">
|
<span className="flex flex-col w-full items-stretch">
|
||||||
{last7DaysConvos.map((tRoom) => (
|
{last7DaysConvos.map((tRoom) => (
|
||||||
<ConversationFullRow key={tRoom.id} conversation={tRoom} />
|
<ConversationFullRow
|
||||||
|
key={tRoom.id}
|
||||||
|
conversation={tRoom}
|
||||||
|
handleJoinLive={handleJoinLive}
|
||||||
|
handleLeaveLive={handleLeaveLive}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -129,7 +272,12 @@ export default function Conversations() {
|
|||||||
</span>
|
</span>
|
||||||
<span className="flex flex-col w-full items-stretch">
|
<span className="flex flex-col w-full items-stretch">
|
||||||
{earlierMonthConvos.map((tRoom) => (
|
{earlierMonthConvos.map((tRoom) => (
|
||||||
<ConversationFullRow key={tRoom.id} conversation={tRoom} />
|
<ConversationFullRow
|
||||||
|
key={tRoom.id}
|
||||||
|
conversation={tRoom}
|
||||||
|
handleJoinLive={handleJoinLive}
|
||||||
|
handleLeaveLive={handleLeaveLive}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -145,7 +293,12 @@ export default function Conversations() {
|
|||||||
</span>
|
</span>
|
||||||
<span className="flex flex-col w-full items-stretch">
|
<span className="flex flex-col w-full items-stretch">
|
||||||
{oldJunkConvos.map((tRoom) => (
|
{oldJunkConvos.map((tRoom) => (
|
||||||
<ConversationFullRow key={tRoom.id} conversation={tRoom} />
|
<ConversationFullRow
|
||||||
|
key={tRoom.id}
|
||||||
|
conversation={tRoom}
|
||||||
|
handleJoinLive={handleJoinLive}
|
||||||
|
handleLeaveLive={handleLeaveLive}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -384,6 +384,10 @@ export const checkIncomingMessageSelector = selectorFamily({
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!currConvo?.lastActivityDate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currConvo.lastActivityDate.toDate() >
|
currConvo.lastActivityDate.toDate() >
|
||||||
currUserConvoAssoc.lastInteractionDate?.toDate()
|
currUserConvoAssoc.lastInteractionDate?.toDate()
|
||||||
|
|||||||
Reference in New Issue
Block a user