Files
nirvana-v3/packages/desktop/src/controller/lineDataProvider.tsx
T

372 lines
12 KiB
TypeScript

import { $jwtToken, $selectedLineId } from "./recoil";
import {
ConnectToLineRequest,
ServerRequestChannels,
ServerResponseChannels,
SomeoneConnectedResponse,
SomeoneTunedResponse,
SomeoneUntunedFromLineResponse,
StartBroadcastingRequest,
StopBroadcastingRequest,
TuneToLineRequest,
UserStartedBroadcastingResponse,
UserStoppedBroadcastingResponse,
} from "@nirvana/core/sockets/channels";
import React, { useContext, useState } from "react";
import { Socket, io } from "socket.io-client";
import { useCallback, useEffect } from "react";
import { useGetUserDetails, useUserLines } from "./index";
import { LineMemberState } from "@nirvana/core/models/line.model";
import MasterLineData from "@nirvana/core/models/masterLineData.model";
import { User } from "@nirvana/core/models";
import { queryClient } from "../pages/nirvanaApp";
import toast from "react-hot-toast";
import { useRecoilValue } from "recoil";
let $ws: Socket;
function useSocketHandler(linesData: MasterLineData[]) {
const jwtToken = useRecoilValue($jwtToken);
const [linesMap, setLinesMap] = useState<LineIdToMasterLine>({});
/**
* handle ws connection
*/
useEffect(() => {
$ws = io("http://localhost:5000", {
query: { token: jwtToken },
});
$ws.on("connect", () => toast.success("you are connected"));
// client-side errors
$ws.on("connect_error", (err) => {
console.error(err.message); // prints the message associated with the error
toast.error("sorry...this is our bad...please refresh with cmd + r");
// force refetch of server status as well as these generally go hand in hand
// this should overall remount this component currently which is what we want for new data
queryClient.invalidateQueries("SERVER_CHECK");
});
}, []);
/**
* initiate listeners
*/
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
);
setLinesMap((prevLinesMap) => {
const newMap = { ...prevLinesMap };
if (newMap[res.lineId])
newMap[res.lineId].connectedMemberIds = [
...(newMap[res.lineId]?.connectedMemberIds ?? []),
res.userId,
];
return newMap;
});
}
);
// 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
);
// 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
setLinesMap((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
) {
console.log(
"updated my line member state for this line | is toggle tuned in: ",
res.toggledIn
);
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
}
return newMap;
});
}
);
$ws.on(
ServerResponseChannels.SOMEONE_UNTUNED_FROM_LINE,
(res: SomeoneUntunedFromLineResponse) => {
console.log(
`here are all of updated users in the tuned in room`,
res.allTunedIntoUserIds
);
// 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
setLinesMap((prevLinesMap) => {
const newMap = { ...prevLinesMap };
// TODO: update the relevant lineMember (based on which userId is given): state and last visit date if current user is joining
if (newMap[res.lineId])
newMap[res.lineId].tunedInMemberIds = res.allTunedIntoUserIds;
return newMap;
});
}
);
$ws.on(
ServerResponseChannels.SOMEONE_STARTED_BROADCASTING,
(res: UserStartedBroadcastingResponse) => {
console.log("someone is starting to broadcast");
setLinesMap((prevLinesMap) => {
const newMap = { ...prevLinesMap };
if (newMap[res.lineId])
newMap[res.lineId].currentBroadcastersUserIds = [
...(newMap[res.lineId].currentBroadcastersUserIds ?? []),
res.userId,
];
return newMap;
});
}
);
$ws.on(
ServerResponseChannels.SOMEONE_STOPPED_BROADCASTING,
(res: UserStoppedBroadcastingResponse) => {
setLinesMap((prevLinesMap) => {
const newMap = { ...prevLinesMap };
if (newMap[res.lineId]?.currentBroadcastersUserIds) {
newMap[res.lineId].currentBroadcastersUserIds = newMap[
res.lineId
].currentBroadcastersUserIds.filter(
(broadcasterUserId) => broadcasterUserId !== res.userId
);
}
return newMap;
});
}
);
}, [setLinesMap]);
/** handle initial data coming in and creating the initial line map
* and doing initial connections/tune ins ?could trigger this later?
*/
useEffect(() => {
if (linesData?.length > 0) {
console.log("going to make initial connections and create lines map");
console.log(linesData);
setLinesMap((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 };
linesData.forEach((masterLine) => {
const lineId = masterLine.lineDetails._id.toString();
newMap[lineId] = masterLine;
handleConnectToLine(lineId);
// tune into lines that I should be
if (masterLine.currentUserMember.state === LineMemberState.TUNED) {
// don't need to turn toggle on, it's already on
// ! CHANGE PARADIGM TO NOT INCLUDING TOGGLING IN REQUESTS WITH THE SAME AS TUNING IN
// right now, writing just because of later logic
handleTuneToLine(lineId, true);
}
});
return newMap;
});
}
}, [linesData, setLinesMap]);
/** handlers for emitting events to server */
const handleConnectToLine = useCallback(
(lineId: string) => {
$ws.emit(
ServerRequestChannels.CONNECT_TO_LINE,
new ConnectToLineRequest(lineId)
);
},
[$ws]
);
/**
* toggle into a specific line
* @param temporary: denotes whether we are just listening in or want to persist "toggling" it on so that it shows up in overlay
* TODO: have loading state for this particular part of context value
*/
const handleTuneToLine = useCallback(
(lineId: string, turnToggleOn: boolean = false) => {
// they already are in the socket room for updates including media connections and disconnections
// but set the flag so that the line row can know whether or not to start the webrtc process
// and know when to get out or disconnect from the webrtc when the flag turns off
$ws.emit(
ServerRequestChannels.TUNE_INTO_LINE,
new TuneToLineRequest(lineId, turnToggleOn)
);
},
[$ws]
);
/**
* This is when the user wants to tell everyone that they are streaming/broadcasting/buzzing to
* a specific line, whether they are toggle tuned or temporarily tuned in
*
* Note: They could be using push to talk or toggle broadcast
*
* Note: this is handling the data transmission as is the responsibility of this overall context provider
* and not necessarily the aspect of enabling and disabling streaming as webrtc is reliable enough for that
*
* @param lineId the line that the current user is talking into
*/
const handleStartBroadcast = useCallback(
(lineId: string) => {
// emit telling people
$ws.emit(
ServerRequestChannels.BROADCAST_TO_LINE,
new StartBroadcastingRequest(lineId)
);
},
[$ws]
);
const handleStopBroadcast = useCallback(
(lineId: string) => {
// emit telling people
$ws.emit(
ServerRequestChannels.STOP_BROADCAST_TO_LINE,
new StopBroadcastingRequest(lineId)
);
// ?handle recording here as well? or record the incoming stream instead?
// ?could just take in the audiochunks here and all, but not sure yet
},
[$ws]
);
/**
* get more audio blocks for a certain line
*/
const handleFetchMoreAudioBlocks = useCallback(
(lineId: string) => {
// simple axios fetch for this specific block
// update the specific line in lines map
// set the state to trigger the re-renders in the tree
},
[$ws]
);
// TODO: move all of these emitters to just having child views doing the work here
return {
linesMap,
handleConnectToLine,
handleTuneToLine,
handleStartBroadcast,
handleStopBroadcast,
handleFetchMoreAudioBlocks,
};
}
type LineIdToMasterLine = {
[lineId: string]: MasterLineData;
};
interface ILineDataContext {
// represents the modified and up to date lines data with availability from session
linesMap: LineIdToMasterLine;
relevantUsers: User[];
// in case some components need this like in the peer handling
$ws: Socket;
}
const LineDataContext = React.createContext<
Partial<ILineDataContext & ReturnType<typeof useSocketHandler>>
>({
linesMap: {},
relevantUsers: [],
$ws: undefined,
});
export function LineDataProvider({ children }) {
// TODO: have internal loading to prevent showing even router and all if something is not ready
// or have it within each property within the context value
// persistent store of lines
// ?just do simple synchronous axios/fetch in useEffect and manage isLoading ourselves?
const { data: basicUserLinesData } = useUserLines();
const { linesMap, ...handlers } = useSocketHandler(
basicUserLinesData?.data?.masterLines
);
if (!$ws) {
return <span>attempting to connect you for the here and now...</span>;
}
const value: ILineDataContext & ReturnType<typeof useSocketHandler> = {
linesMap, // TODO send the updated map instead of this array
relevantUsers: [],
$ws,
...handlers,
};
return (
<LineDataContext.Provider value={value}>
{children}
</LineDataContext.Provider>
);
}
export function useLineDataProvider() {
return useContext(LineDataContext);
}