diff --git a/packages/api/routes/line.ts b/packages/api/routes/line.ts index 32f9944..1236511 100644 --- a/packages/api/routes/line.ts +++ b/packages/api/routes/line.ts @@ -1,24 +1,21 @@ -import { JwtClaims, authCheck } from "../middleware/auth"; -import { - Line, - LineMember, - LineMemberState, -} from "@nirvana/core/models/line.model"; -import express, { Application, Request, Response } from "express"; +import { JwtClaims, authCheck } from '../middleware/auth'; +import { Line, LineMember, LineMemberState } from '@nirvana/core/models/line.model'; +import express, { Application, Request, Response } from 'express'; -import Content from "@nirvana/core/models/content.model"; -import CreateLineRequest from "@nirvana/core/requests/createLine.request"; -import GetConversationDetailsResponse from "@nirvana/core/responses/getConversationDetails.response"; -import GetDmConversationByOtherUserIdResponse from "@nirvana/core/responses/getDmConversationByOtherUserId.response"; -import GetUserLinesResponse from "@nirvana/core/responses/getUserLines.response"; -import { LineService } from "../services/line.service"; -import MasterLineData from "@nirvana/core/models/masterLineData.model"; -import NirvanaResponse from "../../core/responses/nirvanaResponse"; -import { ObjectId } from "mongodb"; -import Relationship from "@nirvana/core/models/relationship.model"; -import { User } from "@nirvana/core/models/user.model"; -import { UserService } from "../services/user.service"; -import { collections } from "../services/database.service"; +import Content from '@nirvana/core/models/content.model'; +import CreateLineRequest from '@nirvana/core/requests/createLine.request'; +import GetConversationDetailsResponse from '@nirvana/core/responses/getConversationDetails.response'; +import GetDmConversationByOtherUserIdResponse from '@nirvana/core/responses/getDmConversationByOtherUserId.response'; +import GetUserLinesResponse from '@nirvana/core/responses/getUserLines.response'; +import { LineService } from '../services/line.service'; +import MasterLineData from '@nirvana/core/models/masterLineData.model'; +import NirvanaResponse from '@nirvana/core/responses/nirvanaResponse'; +import { ObjectId } from 'mongodb'; +import Relationship from '@nirvana/core/models/relationship.model'; +import { User } from '@nirvana/core/models/user.model'; +import { UserService } from '../services/user.service'; +import { collections } from '../services/database.service'; +import UpdateLineMemberState from '@nirvana/core/requests/updateLineMemberState.request'; export default function getLineRoutes() { const router = express.Router(); @@ -29,13 +26,16 @@ export default function getLineRoutes() { // router.get("/:otherUserGoogleUserId", authCheck, getConversationDetails); // create a line - router.post("/", authCheck, createLine); + router.post('/', authCheck, createLine); // get all of user's lines - router.get("/", authCheck, getUserLines); + router.get('/', authCheck, getUserLines); + + // toggle tune into a line + router.post('/:lineId/state', authCheck, updateLineMemberState); // get conversation between user and other user - router.get("/dm/:otherUserId", authCheck, getDmByOtherUserId); + router.get('/dm/:otherUserId', authCheck, getDmByOtherUserId); return router; } @@ -54,7 +54,7 @@ async function getDmByOtherUserId(req: Request, res: Response) { res.status(200).json(); return; - res.status(205).json("no such conversation between you two"); + res.status(205).json('no such conversation between you two'); } catch (error) { res.status(500).json(error); } @@ -66,7 +66,7 @@ async function createLine(req: Request, res: Response) { console.log(req.body); if (!reqObj?.otherMemberIds.length) { - res.status(400).json("must provide member Ids"); + res.status(400).json('must provide member Ids'); return; } @@ -77,7 +77,7 @@ async function createLine(req: Request, res: Response) { reqObj.lineName ?? undefined, new Date(), new Date(), - new ObjectId() + new ObjectId(), ); // TODO: validate that users exists before creating line members @@ -86,67 +86,74 @@ async function createLine(req: Request, res: Response) { const newLineMember = new LineMember( newLine._id!, new ObjectId(memId), - LineMemberState.INBOX + LineMemberState.INBOX, ); return newLineMember; }) ?? []; lineMembers.push( - new LineMember( - newLine._id!, - new ObjectId(userInfo.userId), - LineMemberState.INBOX - ) + new LineMember(newLine._id!, new ObjectId(userInfo.userId), LineMemberState.INBOX), ); - const transactionResult = await LineService.createLine( - newLine, - lineMembers - ); + const transactionResult = await LineService.createLine(newLine, lineMembers); transactionResult ? res.status(200).json(new NirvanaResponse(newLine)) - : res - .status(400) - .json( - new NirvanaResponse(undefined, new Error("unable to create line")) - ); + : res.status(400).json(new NirvanaResponse(undefined, new Error('unable to create line'))); } catch (error) { console.log(error); res.status(500).json(error); } } +async function updateLineMemberState(req: Request, res: Response) { + try { + const userInfo = res.locals.userInfo as JwtClaims; + const { lineId } = req.params; + const request = req.body as UpdateLineMemberState; + + // TODO: validation to check if user is actually a member of the line + + const result = await LineService.updateLineMemberState( + lineId, + userInfo.userId, + request.newState, + ); + + return result?.ok + ? res.status(200).json(new NirvanaResponse("successfully updated line member's state")) + : res + .status(400) + .json(new NirvanaResponse(undefined, new Error('not updated...something went wrong'))); + } catch (error) { + res.status(500).json(new NirvanaResponse(undefined, error as Error)); + } +} + async function getUserLines(req: Request, res: Response) { try { const userInfo = res.locals.userInfo as JwtClaims; // get all of user's lineMember entries - const userLineMembers = await LineService.getLineMembersByUserId( - userInfo.userId - ); + const userLineMembers = await LineService.getLineMembersByUserId(userInfo.userId); if (!userLineMembers?.length) { const resObj = new GetUserLinesResponse([]); - res.json( - new NirvanaResponse(resObj, undefined, "this user is not in any lines") - ); + res.json(new NirvanaResponse(resObj, undefined, 'this user is not in any lines')); return; } - const lineIds = - userLineMembers?.map((lineMember) => lineMember.lineId) ?? []; + const lineIds = userLineMembers?.map((lineMember) => lineMember.lineId) ?? []; // this will include the current user lineMember association to the line const allLineMembers = await LineService.getLineMembersInLines(lineIds); const allLinesUsersIds: ObjectId[] = []; allLineMembers?.map((currentLineMember) => { - if (currentLineMember?.userId) - allLinesUsersIds.push(currentLineMember.userId); + if (currentLineMember?.userId) allLinesUsersIds.push(currentLineMember.userId); }) ?? []; // get all users relevant here @@ -174,34 +181,28 @@ async function getUserLines(req: Request, res: Response) { // get the user lineMember assoc out of the list of the lineMembers for this line const userLineMember = associatedLineMembersForLine?.find( (currentLineMemberForLine) => - currentLineMemberForLine.userId.toString() === userInfo.userId + currentLineMemberForLine.userId.toString() === userInfo.userId, ); if (userLineMember) { // take out the current user from the "other" line members list now associatedLineMembersForLine = associatedLineMembersForLine.filter( - (currentLineMember) => - currentLineMember.userId.toString() !== userInfo.userId + (currentLineMember) => currentLineMember.userId.toString() !== userInfo.userId, ); // get the user objects for all of the other members const otherUsers: User[] = []; associatedLineMembersForLine.forEach((currentLineMember) => { const foundUserObject = allRelevantUsers?.find((currentUser) => - currentUser._id?.equals(currentLineMember.userId) + currentUser._id?.equals(currentLineMember.userId), ); if (foundUserObject) otherUsers.push(foundUserObject); }); masterLines.push( - new MasterLineData( - currentLine, - userLineMember, - associatedLineMembersForLine, - otherUsers - ) + new MasterLineData(currentLine, userLineMember, associatedLineMembersForLine, otherUsers), ); } }); diff --git a/packages/api/sockets/index.ts b/packages/api/sockets/index.ts index 92a4d63..5b4283a 100644 --- a/packages/api/sockets/index.ts +++ b/packages/api/sockets/index.ts @@ -7,6 +7,7 @@ import { ServerRequestChannels, ServerResponseChannels, SomeoneConnectedResponse, + SomeoneDisconnectedResponse, SomeoneTunedResponse, SomeoneUntunedFromLineResponse, StartBroadcastingRequest, @@ -84,23 +85,12 @@ export default function InitializeWs(io: any) { const roomName = `connectedLine:${req.lineId}`; socket.join(roomName); - console.log(`${socket.id} now in rooms ${socket.rooms}`); - - const clientUserIdsInRoom = [...(io.sockets.adapter.rooms.get(roomName) ?? [])].map( - (otherUserSocketId: string) => socketIdsToUserIds[otherUserSocketId], - ); - io.in(roomName).emit( ServerResponseChannels.SOMEONE_CONNECTED_TO_LINE, - new SomeoneConnectedResponse(req.lineId, userInfo.userId, clientUserIdsInRoom), + new SomeoneConnectedResponse(req.lineId, userInfo.userId), ); }); - /** - * TODO: handle when user wants to completely leave a line (delete or removed from one) - */ - socket.on(ServerRequestChannels.DISCONNECT_FROM_LINE, () => console.log('not implemented')); - /** TUNE | User tunes into the line either temporarily or toggled in */ socket.on(ServerRequestChannels.TUNE_INTO_LINE, async (req: TuneToLineRequest) => { console.log(`${socket.id} user TUNED into room for line ${req.lineId}`); @@ -108,38 +98,12 @@ export default function InitializeWs(io: any) { const roomName = `tunedLine:${req.lineId}`; socket.join(roomName); - console.log(`${socket.id} now in rooms ${Object.keys(socket.rooms)}`); - - // persist tuning in if user is toggle tuning in - if (req.keepTunedIn) { - await LineService.updateLineMemberState( - req.lineId, - userInfo.userId, - LineMemberState.TUNED, - ); - } else { - await LineService.updateLineMemberState( - req.lineId, - userInfo.userId, - LineMemberState.INBOX, - ); - } - - const clientUserIdsInRoom = [...(io.sockets.adapter.rooms.get(roomName) ?? [])].map( - (otherUserSocketId: string) => socketIdsToUserIds[otherUserSocketId], - ); - // we want to notify everyone connected to the line even if they are not tuned in const connectedLineRoomName = `connectedLine:${req.lineId}`; io.in(connectedLineRoomName).emit( ServerResponseChannels.SOMEONE_TUNED_INTO_LINE, - new SomeoneTunedResponse( - req.lineId, - userInfo.userId, - clientUserIdsInRoom, - req.keepTunedIn, - ), + new SomeoneTunedResponse(req.lineId, userInfo.userId), ); }); @@ -151,18 +115,14 @@ export default function InitializeWs(io: any) { const roomName = `tunedLine:${req.lineId}`; socket.leave(roomName); - console.log('someone left room'); - - const clientUserIdsInRoom = [...(io.sockets.adapter.rooms.get(roomName) ?? [])].map( - (otherUserSocketId: string) => socketIdsToUserIds[otherUserSocketId], - ); + console.log(`${userInfo.userId} left room: ${roomName}`); // we want to notify everyone connected to the line even if they are not tuned in const connectedLineRoomName = `connectedLine:${req.lineId}`; io.in(connectedLineRoomName).emit( ServerResponseChannels.SOMEONE_UNTUNED_FROM_LINE, - new SomeoneUntunedFromLineResponse(req.lineId, userInfo.userId, clientUserIdsInRoom), + new SomeoneUntunedFromLineResponse(req.lineId, userInfo.userId), ); }); @@ -186,21 +146,6 @@ export default function InitializeWs(io: any) { ); }); - // socket.on(SocketChannels.SEND_SIGNAL, async (payload: SendSignal) => { - // console.log(payload); - - // const sendingBackData: ReceiveSignal = { - // simplePeerSignal: payload.simplePeerSignal, - // senderUserSocketId: socket.id, - // isGoingBackToInitiator: payload.isAnswerer ? true : false, - // }; - - // io.to(payload.userSocketIdToSignal).emit( - // SocketChannels.RECEIVE_SIGNAL, - // sendingBackData - // ); - // }); - // tell the proper other user to create a local peer object for the one on one mesh connection socket.on(ServerRequestChannels.RTC_CALL_REQUEST, (req: RtcCallRequest) => { const userSocketId = userIdsToSocketIds[req.userIdToCall]; @@ -220,58 +165,25 @@ export default function InitializeWs(io: any) { ); }); - // TODO: not complete - socket.on(ServerRequestChannels.GOING_INTO_FLOW_STATE, () => { - // change user object to persist this state - - // get all of the connectedLine rooms of this person, and tell them that user is going into flow state - - for (const roomName of socket.rooms) { - if (roomName !== socket.id) { - const lineId = roomName.split(':')[1]; - if (roomName.includes('connectedLine')) { - // get fresh list of tuned in folks without me - const clientUserIdsInRoom = [...(io.sockets.adapter.rooms.get(roomName) ?? [])] - .filter((mappedSocketId) => mappedSocketId !== socket.id) - .map((otherUserSocketId: string) => socketIdsToUserIds[otherUserSocketId]); - - // io.in(roomName).emit( - // ServerResponseChannels.SOMEONE_GOING_INTO_FLOW_STATE, - // new SomeoneGoingIntoFlowState( - - // userInfo.userId - - // ) - // ); - } - } - } - }); - - // tell all connected people that I am disconnecting - // tell tuned in folks that I am leaving the room + // tell all people in all my lines that I am disconnecting or untuning // tell the tuned in folks the new list of socket.on('disconnecting', (reason: any) => { - console.log(reason); + console.log(`someone disconnecting: ${reason}`); console.log(socket.rooms); + for (const roomName of socket.rooms) { if (roomName !== socket.id) { const lineId = roomName.split(':')[1]; - if (roomName.includes('tunedLine')) { - // get fresh list of tuned in folks without me - const clientUserIdsInRoom = [...(io.sockets.adapter.rooms.get(roomName) ?? [])] - .filter((mappedSocketId) => mappedSocketId !== socket.id) - .map((otherUserSocketId: string) => socketIdsToUserIds[otherUserSocketId]); - io.in(roomName).emit( - ServerResponseChannels.SOMEONE_UNTUNED_FROM_LINE, - new SomeoneUntunedFromLineResponse(lineId, userInfo.userId, clientUserIdsInRoom), - ); - } else if (roomName.includes('connectedLine')) { - //TODO: p3: client doesn't really to know right now in our flow as this list is not really used - // io.in(roomName).emit(ServerResponseChannels.SOMEONE_UNTUNED_FROM_LINE, new SomeoneDisconnected(lineId, userInfo.userId, clientUserIdsInRoom)); - } + const roomToTell = roomName.includes('tunedLine') + ? `connectedLine:${lineId}` + : roomName; + + io.in(roomToTell).emit( + ServerResponseChannels.SOMEONE_DISCONNECTED_FROM_LINE, + new SomeoneDisconnectedResponse(lineId, userInfo.userId), + ); } } }); @@ -280,16 +192,6 @@ export default function InitializeWs(io: any) { socket.on('disconnect', () => { delete socketIdsToUserIds[socket.id]; delete userIdsToSocketIds[userInfo.userId]; - - // get all of the rooms of this socket - // notify everyone of this disconnection - - console.log('user disconnected'); - console.log(`list of sockets mappings in memory: ${socketIdsToUserIds}`); - - console.log(socket.rooms); - - // ! NOTIFY ALL CONNECTED LINES SO THAT THEY CAN REMOVE FROM THEIR SESSION CONNECTED USERS ARRAY AND TUNED IN ARRAY }); }); } diff --git a/packages/core/requests/updateLineMemberState.request.ts b/packages/core/requests/updateLineMemberState.request.ts new file mode 100644 index 0000000..3e15af4 --- /dev/null +++ b/packages/core/requests/updateLineMemberState.request.ts @@ -0,0 +1,4 @@ +import { LineMemberState } from '../models/line.model'; +export default class UpdateLineMemberState { + constructor(public newState: LineMemberState) {} +} diff --git a/packages/core/requests/updateRelationshipState.request.ts b/packages/core/requests/updateRelationshipState.request.ts deleted file mode 100644 index 5c6afa2..0000000 --- a/packages/core/requests/updateRelationshipState.request.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ObjectId } from "mongodb"; -import { RelationshipState } from "../models/relationship.model"; - -export default class UpdateRelationshipStateRequest { - constructor( - public relationshipId: ObjectId, - public newState: RelationshipState - ) {} -} diff --git a/packages/core/sockets/channels.ts b/packages/core/sockets/channels.ts index eba1417..7699823 100644 --- a/packages/core/sockets/channels.ts +++ b/packages/core/sockets/channels.ts @@ -1,68 +1,68 @@ // ! NOTE: these are legacy and too much thinking in the developers head to understand the flow enum SocketChannels { - SEND_AUDIO_CLIP = "SEND_AUDIO_CLIP", - SEND_USER_STATUS_UPDATE = "SEND_USER_STATUS_UPDATE", + SEND_AUDIO_CLIP = 'SEND_AUDIO_CLIP', + SEND_USER_STATUS_UPDATE = 'SEND_USER_STATUS_UPDATE', - SEND_STARTED_SPEAKING = "SEND_STARTED_SPEAKING", - SEND_STOPPED_SPEAKING = "SEND_STOPPED_SPEAKING", + SEND_STARTED_SPEAKING = 'SEND_STARTED_SPEAKING', + SEND_STOPPED_SPEAKING = 'SEND_STOPPED_SPEAKING', - JOIN_LIVE_ROOM = "JOIN_LIVE_ROOM", - GET_ALL_ACTIVE_SOCKET_IDS = "GET_ALL_ACTIVE_SOCKET_IDS", + JOIN_LIVE_ROOM = 'JOIN_LIVE_ROOM', + GET_ALL_ACTIVE_SOCKET_IDS = 'GET_ALL_ACTIVE_SOCKET_IDS', - SEND_SIGNAL = "SEND_SIGNAL", + SEND_SIGNAL = 'SEND_SIGNAL', - RECEIVE_SIGNAL = "RECEIVE_SIGNAL", + RECEIVE_SIGNAL = 'RECEIVE_SIGNAL', // v3 /** * when someone connects to a line, whether tuned in or not */ - CONNECT_TO_LINE = "CONNECT_TO_LINE", - SOMEONE_CONNECTED_TO_LINE = "SOMEONE_CONNECTED_TO_LINE", + CONNECT_TO_LINE = 'CONNECT_TO_LINE', + SOMEONE_CONNECTED_TO_LINE = 'SOMEONE_CONNECTED_TO_LINE', - TUNE_TO_LINE = "TUNE_TO_LINE", - SOMEONE_TUNED_TO_LINE = "SOMEONE_TUNED_TO_LINE", + TUNE_TO_LINE = 'TUNE_TO_LINE', + SOMEONE_TUNED_TO_LINE = 'SOMEONE_TUNED_TO_LINE', - SOMEONE_UNTUNED_FROM_LINE = "SOMEONE_UNTUNED_FROM_LINE", + SOMEONE_UNTUNED_FROM_LINE = 'SOMEONE_UNTUNED_FROM_LINE', - USER_BROADCAST_PUSH_PULL = "USER_BROADCAST_PUSH_PULL", + USER_BROADCAST_PUSH_PULL = 'USER_BROADCAST_PUSH_PULL', } // ! MAKE SURE THAT THE ENUMS BETWEEN REQUEST AND RESPONSE DON'T OVERLAP??? // ?maybe won't matter since it's different for server and client? export enum ServerRequestChannels { - CONNECT_TO_LINE = "CONNECT_TO_LINE", + CONNECT_TO_LINE = 'CONNECT_TO_LINE', - DISCONNECT_FROM_LINE = "DISCONNECT_FROM_LINE", // TODO: not implementing now + DISCONNECT_FROM_LINE = 'DISCONNECT_FROM_LINE', // TODO: not implementing now - TUNE_INTO_LINE = "TUNE_INTO_LINE", // pass in if user wants to toggle/persist? or is this just temporary? - UNTUNE_FROM_LINE = "UNTUNE_FROM_LINE", + TUNE_INTO_LINE = 'TUNE_INTO_LINE', // pass in if user wants to toggle/persist? or is this just temporary? + UNTUNE_FROM_LINE = 'UNTUNE_FROM_LINE', - BROADCAST_TO_LINE = "BROADCAST_TO_LINE", - STOP_BROADCAST_TO_LINE = "STOP_BROADCAST_TO_LINE", + BROADCAST_TO_LINE = 'BROADCAST_TO_LINE', + STOP_BROADCAST_TO_LINE = 'STOP_BROADCAST_TO_LINE', - RTC_CALL_REQUEST = "RTC_CALL_PREFIX", - RTC_ANSWER_REQUEST = "RTC_ANSWER_REQUEST_PREFIX", + RTC_CALL_REQUEST = 'RTC_CALL_PREFIX', + RTC_ANSWER_REQUEST = 'RTC_ANSWER_REQUEST_PREFIX', - GOING_INTO_FLOW_STATE = "GOING_INTO_FLOW_STATE", + GOING_INTO_FLOW_STATE = 'GOING_INTO_FLOW_STATE', } export enum ServerResponseChannels { - SOMEONE_CONNECTED_TO_LINE = "SOMEONE_CONNECTED_TO_LINE", - SOMEONE_DISCONNECTED_FROM_LINE = "SOMEONE_DISCONNECTED_FROM_LINE", + SOMEONE_CONNECTED_TO_LINE = 'SOMEONE_CONNECTED_TO_LINE', + SOMEONE_DISCONNECTED_FROM_LINE = 'SOMEONE_DISCONNECTED_FROM_LINE', - SOMEONE_TUNED_INTO_LINE = "SOMEONE_TUNED_INTO_LINE", // allows all current tuned in folks to create peer objects - SOMEONE_UNTUNED_FROM_LINE = "SOMEONE_UNTUNED_FROM_LINE", // discard peer + SOMEONE_TUNED_INTO_LINE = 'SOMEONE_TUNED_INTO_LINE', // allows all current tuned in folks to create peer objects + SOMEONE_UNTUNED_FROM_LINE = 'SOMEONE_UNTUNED_FROM_LINE', // discard peer - SOMEONE_STARTED_BROADCASTING = "SOMEONE_STARTED_BROADCASTING", //show their stream tracks - SOMEONE_STOPPED_BROADCASTING = "SOMEONE_STOPPED_BROADCASTING", // stop showing their stream tracks + SOMEONE_STARTED_BROADCASTING = 'SOMEONE_STARTED_BROADCASTING', //show their stream tracks + SOMEONE_STOPPED_BROADCASTING = 'SOMEONE_STOPPED_BROADCASTING', // stop showing their stream tracks // sending to the correct room of tunedin folks AND also making sure it's the right event handler in the right handler for this component - RTC_NEW_USER_JOINED_RESPONSE_PREFIX = "RTC_NEW_USER_JOINED_RESPONSE_PREFIX", - RTC_RECEIVING_ANSWER_RESPONSE_PREFIX = "RTC_RECEIVING_ANSWER_RESPONSE_PREFIX", + RTC_NEW_USER_JOINED_RESPONSE_PREFIX = 'RTC_NEW_USER_JOINED_RESPONSE_PREFIX', + RTC_RECEIVING_ANSWER_RESPONSE_PREFIX = 'RTC_RECEIVING_ANSWER_RESPONSE_PREFIX', - SOMEONE_GOING_INTO_FLOW_STATE = "SOMEONE_GOING_INTO_FLOW_STATE", + SOMEONE_GOING_INTO_FLOW_STATE = 'SOMEONE_GOING_INTO_FLOW_STATE', } export default SocketChannels; @@ -71,33 +71,23 @@ export class ConnectToLineRequest { constructor(public lineId: string) {} } export class SomeoneConnectedResponse { - constructor( - public lineId: string, - public userId: string, - public allConnectedIntoUserIds: string[] - ) {} + constructor(public lineId: string, public userId: string) {} } +export class SomeoneDisconnectedResponse { + constructor(public lineId: string, public userId: string) {} +} export class TuneToLineRequest { constructor(public lineId: string, public keepTunedIn: boolean = false) {} } export class SomeoneTunedResponse { - constructor( - public lineId: string, - public userId: string, - public allTunedIntoUserIds: string[], - public toggledIn: boolean = false // tell client if this new user toggled in or just temporary so that they can update the right LineMember assoc - ) {} + constructor(public lineId: string, public userId: string) {} } export class UntuneFromLineRequest { constructor(public lineId: string) {} } export class SomeoneUntunedFromLineResponse { - constructor( - public lineId: string, - public userId: string, - public allTunedIntoUserIds: string[] - ) {} + constructor(public lineId: string, public userId: string) {} } export class StartBroadcastingRequest { @@ -120,11 +110,7 @@ export class SocketEmitter { } export class RtcCallRequest { - constructor( - public lineId: string, - public userIdToCall: string, - public simplePeerSignal: any - ) {} + constructor(public lineId: string, public userIdToCall: string, public simplePeerSignal: any) {} } export class RtcNewUserResponse { @@ -132,11 +118,7 @@ export class RtcNewUserResponse { } export class RtcAnswerRequest { - constructor( - public lineId: string, - public userIdToCall: string, - public simplePeerSignal: any - ) {} + constructor(public lineId: string, public userIdToCall: string, public simplePeerSignal: any) {} } export class RtcReceiveAnswerResponse { diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 9345c3e..e488cab 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -107,6 +107,7 @@ "axios": "^0.26.1", "electron-squirrel-startup": "^1.0.0", "electron-store": "^8.0.1", + "immer": "^9.0.14", "moment": "^2.29.1", "mongodb": "^4.4.1", "react": "^17.0.2", @@ -119,6 +120,7 @@ "recoil": "^0.6.1", "sass": "^1.51.0", "simple-peer": "^9.11.1", - "socket.io-client": "^4.4.1" + "socket.io-client": "^4.4.1", + "use-immer": "^0.7.0" } } diff --git a/packages/desktop/src/providers/RealTimeRoomProvider.tsx b/packages/desktop/src/providers/RealTimeRoomProvider.tsx index 80b1fd6..39a402a 100644 --- a/packages/desktop/src/providers/RealTimeRoomProvider.tsx +++ b/packages/desktop/src/providers/RealTimeRoomProvider.tsx @@ -8,12 +8,15 @@ import { LineMemberState } from '@nirvana/core/models/line.model'; import { ServerResponseChannels, SomeoneConnectedResponse, + SomeoneDisconnectedResponse, SomeoneTunedResponse, SomeoneUntunedFromLineResponse, UserStartedBroadcastingResponse, UserStoppedBroadcastingResponse, } from '@nirvana/core/sockets/channels'; import toast from 'react-hot-toast'; +import { useMap } from 'react-use'; +import { useImmer } from 'use-immer'; type LineIdToMasterLine = { [lineId: string]: MasterLineData; @@ -33,84 +36,125 @@ const RealTimeRoomContext = React.createContext({ }, }); -// handles reads of new data -// keeps listening to incoming socket events to make sure that the realtime rooms map is highly available +/** + * + * handles reads of new data + * keeps listening to incoming socket events to make sure that the realtime rooms map is highly available + * + * on load, we want to grab all of the rooms we are in + * put them in a map + * + * fetch more audio clips, fire off async function to fetch more and add to the room map + * + * Socket Rooms: + * - all people online for a line + * - all tuned in folks on a line...have it selected or toggle tuned + * + * Socket Events: + * - someone connected + * - someone tuned in + * - someone started broadcasting + * - someone stopped broadcasting + * + * - someone disconnected...take them out of the necessary lists + * - someone left x room + * - someone joined x room + * + * - someone added me to line + * + * - someone went into flow state their status + * + * Socket Emissions: + * - join a line + * - tune into a line + * - send audio clip + * - create a line -> send to specific people + * + * REST endpoints: + * - toggle tune or untoggle tune + * - fetch content blocks for line history - react query + */ + export function RealTimeRoomProvider({ children }: { children: React.ReactChild }) { const { rooms } = useRooms(); const { $ws } = useSockets(); - const [realTimeRoomMap, setRealTimeRoomMap] = useState({}); + const [roomMap, updateRoomMap] = useImmer({}); const [selectedLineId, setSelectedLineId] = useState(); useEffect(() => { // when me or anyone just initially connects to line $ws.on(ServerResponseChannels.SOMEONE_CONNECTED_TO_LINE, (res: SomeoneConnectedResponse) => { - console.log( - `connected to line...here are all of the updated in the conected line ${res.lineId}...this isn't reliable considering it's not updated later`, - res.allConnectedIntoUserIds, - ); + console.log(`${res.userId} connected to room ${res.lineId}`); - setRealTimeRoomMap((prevLinesMap) => { - const newMap = { ...prevLinesMap }; + updateRoomMap((draft) => { + if (!draft[res.lineId]) { + toast.error('there was a problem updating rooms!!!'); + return; + } - if (newMap[res.lineId]) - newMap[res.lineId].connectedMemberIds = [ - ...(newMap[res.lineId]?.connectedMemberIds ?? []), - res.userId, - ]; - - return newMap; + if (draft[res.lineId].connectedMemberIds) { + draft[res.lineId].connectedMemberIds.push(res.userId); + } else { + draft[res.lineId].connectedMemberIds = [res.userId]; + } }); }); // someone tuning in, including perhaps me | either toggled in or just temporary $ws.on(ServerResponseChannels.SOMEONE_TUNED_INTO_LINE, (res: SomeoneTunedResponse) => { - console.log(`here are all of updated users in the tuned in room`, res.allTunedIntoUserIds); + console.log(`${res.userId} tuned into line ${res.lineId}`); - // TODO: if toggled in, make sure to update the current line member in the lines map so that - // we can know to untune if user selects another line - - // below, we are setting the list of tuned in folks based on fresh list from the server - // better than just adding and removing? i think so, but have to handle not interrupting existing peer connections as this changes - setRealTimeRoomMap((prevLinesMap) => { - const newMap = { ...prevLinesMap }; - - if (newMap[res.lineId]) { - newMap[res.lineId].tunedInMemberIds = res.allTunedIntoUserIds; - - // if user is me, make sure to show me my updated line member association - if (newMap[res.lineId].currentUserMember?.userId.toString() === res.userId) { - newMap[res.lineId].currentUserMember.lastVisitDate = new Date(); - newMap[res.lineId].currentUserMember.state = res.toggledIn - ? LineMemberState.TUNED - : LineMemberState.INBOX; - } - - // TODO: update the relevant lineMember (based on which userId is given): state and last visit date if current user is joining - // and not just if it's the current user toggling in - // right now, we just want user to know number of folks tuned and no need to expose who is toggle tuned...that lineMember can be stale + updateRoomMap((draft) => { + if (!draft[res.lineId]) { + toast.error('there was a problem updating rooms!!!'); + return; } - return newMap; + if (draft[res.lineId].tunedInMemberIds) { + draft[res.lineId].tunedInMemberIds.push(res.userId); + } else { + draft[res.lineId].tunedInMemberIds = [res.userId]; + } }); }); $ws.on( ServerResponseChannels.SOMEONE_UNTUNED_FROM_LINE, (res: SomeoneUntunedFromLineResponse) => { - console.log(`here are all of updated users in the tuned in room`, res.allTunedIntoUserIds); + console.log(`${res.userId} untuned from ${res.lineId}`); - // TODO: if toggled in, make sure to update the current line member in the lines map so that - // we can know to untune if user selects another line + updateRoomMap((draft) => { + if (!draft[res.lineId]) { + toast.error('there was a problem updating rooms!!!'); + return; + } - setRealTimeRoomMap((prevLinesMap) => { - const newMap = { ...prevLinesMap }; + if (draft[res.lineId].tunedInMemberIds) { + draft[res.lineId].tunedInMemberIds = draft[res.lineId].tunedInMemberIds.filter( + (userId) => userId !== res.userId, + ); + } + }); + }, + ); - // TODO: update the relevant lineMember (based on which userId is given): state and last visit date if current user is joining + // remove them from the line connected list and tuned list if they are there + $ws.on( + ServerResponseChannels.SOMEONE_DISCONNECTED_FROM_LINE, + (res: SomeoneDisconnectedResponse) => { + updateRoomMap((draft) => { + if (!draft[res.lineId]) { + toast.error('there was a problem updating rooms!!!'); + return; + } - if (newMap[res.lineId]) newMap[res.lineId].tunedInMemberIds = res.allTunedIntoUserIds; - - return newMap; + draft[res.lineId].connectedMemberIds = draft[res.lineId].connectedMemberIds?.filter( + (userId) => userId !== res.userId, + ); + draft[res.lineId].tunedInMemberIds = draft[res.lineId].tunedInMemberIds?.filter( + (userId) => userId !== res.userId, + ); }); }, ); @@ -118,18 +162,19 @@ export function RealTimeRoomProvider({ children }: { children: React.ReactChild $ws.on( ServerResponseChannels.SOMEONE_STARTED_BROADCASTING, (res: UserStartedBroadcastingResponse) => { - console.log('someone is starting to broadcast'); + console.log(`${res.userId} is starting to broadcast in ${res.lineId}`); - setRealTimeRoomMap((prevLinesMap) => { - const newMap = { ...prevLinesMap }; + updateRoomMap((draft) => { + if (!draft[res.lineId]) { + toast.error('there was a problem updating rooms!!!'); + return; + } - if (newMap[res.lineId]) - newMap[res.lineId].currentBroadcastersUserIds = [ - ...(newMap[res.lineId].currentBroadcastersUserIds ?? []), - res.userId, - ]; - - return newMap; + if (draft[res.lineId].currentBroadcastersUserIds) { + draft[res.lineId].currentBroadcastersUserIds.push(res.userId); + } else { + draft[res.lineId].currentBroadcastersUserIds = [res.userId]; + } }); }, ); @@ -137,18 +182,19 @@ export function RealTimeRoomProvider({ children }: { children: React.ReactChild $ws.on( ServerResponseChannels.SOMEONE_STOPPED_BROADCASTING, (res: UserStoppedBroadcastingResponse) => { - setRealTimeRoomMap((prevLinesMap) => { - const newMap = { ...prevLinesMap }; + console.log(`${res.userId} is STOPPED BROADCASTING in ${res.lineId}`); - if (newMap[res.lineId]?.currentBroadcastersUserIds) { - newMap[res.lineId].currentBroadcastersUserIds = newMap[ - res.lineId - ].currentBroadcastersUserIds.filter( - (broadcasterUserId) => broadcasterUserId !== res.userId, - ); + updateRoomMap((draft) => { + if (!draft[res.lineId]) { + toast.error('there was a problem updating rooms!!!'); + return; } - return newMap; + if (draft[res.lineId].currentBroadcastersUserIds) { + draft[res.lineId].currentBroadcastersUserIds = draft[ + res.lineId + ].currentBroadcastersUserIds.filter((userId) => userId !== res.userId); + } }); }, ); @@ -157,30 +203,19 @@ export function RealTimeRoomProvider({ children }: { children: React.ReactChild // ?perhaps only remove specific ones? $ws.removeAllListeners(); }; - }, [realTimeRoomMap, $ws]); + }, [roomMap, $ws, updateRoomMap]); - // converts the initial rooms to a map + // converts the initial rooms fetch to a map useEffect(() => { if (rooms.value?.data?.masterLines?.length > 0) { - setRealTimeRoomMap((prevMappings) => { - // go through the lines from the persistent store - - // get all of the id's and map assign to the main object - - // ?prolly have no previous at this point...but I'm okay with override since this - // ?useeffect is triggered on the refetching of the persistent store so we - // ?are prolly looking to do a full app refresh and connection refresh - const newMap = { ...prevMappings }; - + updateRoomMap((draft) => { rooms.value.data.masterLines.forEach((masterLine) => { const lineId = masterLine.lineDetails._id.toString(); - newMap[lineId] = masterLine; + draft[lineId] = masterLine; }); - - return newMap; }); } - }, [rooms.value, setRealTimeRoomMap]); + }, [rooms.value, updateRoomMap]); /** show user line details on click of one line */ const handleSelectLine = useCallback( @@ -192,10 +227,10 @@ export function RealTimeRoomProvider({ children }: { children: React.ReactChild ); return ( - + {children} + +
{JSON.stringify(roomMap)}
); } diff --git a/yarn.lock b/yarn.lock index 76e0729..c845e9a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4743,6 +4743,11 @@ image-size@^0.7.4: resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.7.5.tgz#269f357cf5797cb44683dfa99790e54c705ead04" integrity sha512-Hiyv+mXHfFEP7LzUL/llg9RwFxxY+o9N3JVLIeG5E7iFIFAalxvRU9UZthBdYDEVnzHMgjnKJPPpay5BWf1g9g== +immer@^9.0.14: + version "9.0.14" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.14.tgz#e05b83b63999d26382bb71676c9d827831248a48" + integrity sha512-ubBeqQutOSLIFCUBN03jGeOS6a3DoYlSYwYJTa+gSKEZKU5redJIqkIdZ3JVv/4RZpfcXdAWH5zCNLWPRv2WDw== + immutable@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23" @@ -8955,6 +8960,11 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" +use-immer@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/use-immer/-/use-immer-0.7.0.tgz#e3bfbb806b5e3ff6e37441be74c306d91c1e0962" + integrity sha512-Re4hjrP3a/2ABZjAc0b7AK9s626bnO+H33RO2VUhiDZ2StBz5B663K6WNNlr4QtHWaGUmvLpwt3whFvvWuolQw== + username@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/username/-/username-5.1.0.tgz#a7f9325adce2d0166448cdd55d4985b1360f2508"