cleaning a bunch and adding in the right handlers for sockets much cleaner

This commit is contained in:
talksik
2022-05-14 08:15:34 -05:00
parent ad01220628
commit 7b70c4b023
8 changed files with 257 additions and 330 deletions
+62 -61
View File
@@ -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),
);
}
});
+16 -114
View File
@@ -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
});
});
}
@@ -0,0 +1,4 @@
import { LineMemberState } from '../models/line.model';
export default class UpdateLineMemberState {
constructor(public newState: LineMemberState) {}
}
@@ -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
) {}
}
+40 -58
View File
@@ -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<T> {
}
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 {
+3 -1
View File
@@ -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"
}
}
@@ -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<IRealTimeRoomProvider>({
},
});
// 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<LineIdToMasterLine>({});
const [roomMap, updateRoomMap] = useImmer<LineIdToMasterLine>({});
const [selectedLineId, setSelectedLineId] = useState<string>();
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 (
<RealTimeRoomContext.Provider
value={{ roomsMap: realTimeRoomMap, handleSelectLine, selectedLineId }}
>
<RealTimeRoomContext.Provider value={{ roomsMap: roomMap, handleSelectLine, selectedLineId }}>
{children}
<pre>{JSON.stringify(roomMap)}</pre>
</RealTimeRoomContext.Provider>
);
}
+10
View File
@@ -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"