nicer flow for disconnections, adding socket initialization and such

This commit is contained in:
talksik
2022-05-06 06:30:14 -05:00
parent 0f8902b47d
commit 0d8673d594
9 changed files with 269 additions and 137 deletions
+8 -118
View File
@@ -1,7 +1,9 @@
import express, { Application, Request, Response } from "express";
import GetAllSocketClients from "@nirvana/core/sockets/getAllActiveSocketClients";
import InitializeWs from "./sockets";
import { NextFunction } from "express";
import NirvanaResponse from "@nirvana/core/responses/nirvanaResponse";
import ReceiveSignal from "../core/sockets/receiveSignal";
import SendSignal from "@nirvana/core/sockets/sendSignal";
import SocketChannels from "@nirvana/core/sockets/channels";
@@ -26,14 +28,16 @@ app.get("/", (req: Request, res: Response) => {
res.send("hello world.");
});
app.use("/api", (req: Request, res: Response) => {
res.json(new NirvanaResponse("wohoo, server is healthy"));
});
app.use("/api/user", getUserRoutes());
app.use("/api/search", getSearchRoutes());
app.use("/api/lines", getLineRoutes());
const PORT = 5000;
var server = app.listen(PORT, () => console.log("express running"));
// socket IO stuff
const server = app.listen(PORT, () => console.log("express running"));
const io = require("socket.io")(server, {
// todo: add authentication
@@ -42,118 +46,4 @@ const io = require("socket.io")(server, {
},
});
io.on("connection", function (socket: any) {
// add to map between between googleUserIds and socketId
console.log("a user connected");
// ===== JOIN ====
/** User wants to subscribe to live emissions of a conversation */
socket.on(SocketChannels.JOIN_ROOM, (relationshipId: string) => {
// add this user to the room
console.log(
`${socket.id} user joined room for relationship ${relationshipId}`
);
socket.join(relationshipId);
console.log(`${socket.id} now in rooms ${socket.rooms}`);
});
// ==== UPDATES ====
// can be a change of:
// 1. status...later...do short polling for this instead
// 2. new content: audio clip or link
// 3. someone is starting to speak
// send message to everyone in room except sender...
/** User wants to send some update to a particular room */
socket.on(
SocketChannels.SEND_AUDIO_CLIP,
(relationshipId: string, audioChunks: any) => {
console.log(`new audio chunks received..routing to appropriate room!`);
console.log(relationshipId);
console.log(audioChunks);
io.in(relationshipId).emit(
SocketChannels.SEND_AUDIO_CLIP,
relationshipId,
audioChunks
);
}
);
// tell everyone in a room when someone is starting to speak
socket.on(SocketChannels.SEND_STARTED_SPEAKING, (relationshipId: string) => {
console.log(`started speaking in ${relationshipId}`);
io.in(relationshipId).emit(
SocketChannels.SEND_STARTED_SPEAKING,
relationshipId
);
});
// tell everyone in a room when someone is stopping to speak
socket.on(SocketChannels.SEND_STOPPED_SPEAKING, (relationshipId: string) => {
console.log(`stopped speaking in ${relationshipId}`);
io.in(relationshipId).emit(
SocketChannels.SEND_STOPPED_SPEAKING,
relationshipId
);
});
// change of user status to all of users' rooms and db update
socket.on(
SocketChannels.SEND_USER_STATUS_UPDATE,
async (userGoogleId: string, newStatus: UserStatus) => {
console.log("new status for user");
const resultUpdate = await UserService.updateUserStatus(
userGoogleId,
newStatus
);
// tell all rooms that the user is part of
// that this user has updated their status
if (resultUpdate?.modifiedCount) {
socket.rooms.forEach((roomId: string) => {
io.in(roomId).emit(
SocketChannels.SEND_USER_STATUS_UPDATE,
userGoogleId,
newStatus
);
});
}
}
);
socket.on(SocketChannels.JOIN_LIVE_ROOM, async () => {
// TODO: only get the socket ids of the relevant rooms for this user
// return all Socket instances
const allConnectedSockets = Array.from(await io.of("/").sockets.keys());
io.to(socket.id).emit(SocketChannels.GET_ALL_ACTIVE_SOCKET_IDS, {
socketIds: allConnectedSockets,
} as GetAllSocketClients);
});
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
);
});
// ==== DISCONNECT ====
socket.on("disconnect", () => {
console.log("user disconnected");
});
});
InitializeWs(io);
+170
View File
@@ -0,0 +1,170 @@
import GetAllSocketClients from "@nirvana/core/sockets/getAllActiveSocketClients";
import { JwtClaims } from "../middleware/auth";
import ReceiveSignal from "@nirvana/core/sockets/receiveSignal";
import SendSignal from "@nirvana/core/sockets/sendSignal";
import SocketChannels from "@nirvana/core/sockets/channels";
import { UserService } from "../services/user.service";
import { UserStatus } from "@nirvana/core/models/user.model";
import { loadConfig } from "../config";
const jwt = require("jsonwebtoken");
const config = loadConfig();
export default function InitializeWs(io: any) {
console.log("initializing web sockets");
return io
.use(function (socket: any, next: any) {
console.log("authenticating user...");
try {
const { token } = socket.handshake.query;
// verify jwt token with our api secret
var decoded: JwtClaims = jwt.verify(token, config.JWT_TOKEN_SECRET);
socket.userInfo = decoded;
next();
} catch (error) {
console.error(error);
next(new Error("WS Authentication Error"));
}
})
.on("connection", function (socket: any) {
const userInfo: JwtClaims = socket.userInfo;
console.log(
`a user connected | user Id: ${userInfo.userId} and name: ${userInfo.name}`
);
socket.on("test", () => {
console.log("asdf");
});
// ?verification that user is in a particular line to be tuned into it or just generally in it?
// regular socket rooms for information for all clients in a specific line
// another namespace or so for clients tuned into certain lines
// ===== JOIN ====
/** User wants to subscribe to live emissions of a conversation */
socket.on(SocketChannels.JOIN_ROOM, (relationshipId: string) => {
// add this user to the room
console.log(
`${socket.id} user joined room for relationship ${relationshipId}`
);
socket.join(relationshipId);
console.log(`${socket.id} now in rooms ${socket.rooms}`);
});
// ==== UPDATES ====
// can be a change of:
// 1. status...later...do short polling for this instead
// 2. new content: audio clip or link
// 3. someone is starting to speak
// send message to everyone in room except sender...
/** User wants to send some update to a particular room */
socket.on(
SocketChannels.SEND_AUDIO_CLIP,
(relationshipId: string, audioChunks: any) => {
console.log(
`new audio chunks received..routing to appropriate room!`
);
console.log(relationshipId);
console.log(audioChunks);
io.in(relationshipId).emit(
SocketChannels.SEND_AUDIO_CLIP,
relationshipId,
audioChunks
);
}
);
// tell everyone in a room when someone is starting to speak
socket.on(
SocketChannels.SEND_STARTED_SPEAKING,
(relationshipId: string) => {
console.log(`started speaking in ${relationshipId}`);
io.in(relationshipId).emit(
SocketChannels.SEND_STARTED_SPEAKING,
relationshipId
);
}
);
// tell everyone in a room when someone is stopping to speak
socket.on(
SocketChannels.SEND_STOPPED_SPEAKING,
(relationshipId: string) => {
console.log(`stopped speaking in ${relationshipId}`);
io.in(relationshipId).emit(
SocketChannels.SEND_STOPPED_SPEAKING,
relationshipId
);
}
);
// change of user status to all of users' rooms and db update
socket.on(
SocketChannels.SEND_USER_STATUS_UPDATE,
async (userGoogleId: string, newStatus: UserStatus) => {
console.log("new status for user");
const resultUpdate = await UserService.updateUserStatus(
userGoogleId,
newStatus
);
// tell all rooms that the user is part of
// that this user has updated their status
if (resultUpdate?.modifiedCount) {
socket.rooms.forEach((roomId: string) => {
io.in(roomId).emit(
SocketChannels.SEND_USER_STATUS_UPDATE,
userGoogleId,
newStatus
);
});
}
}
);
socket.on(SocketChannels.JOIN_LIVE_ROOM, async () => {
// TODO: only get the socket ids of the relevant rooms for this user
// return all Socket instances
const allConnectedSockets = Array.from(await io.of("/").sockets.keys());
io.to(socket.id).emit(SocketChannels.GET_ALL_ACTIVE_SOCKET_IDS, {
socketIds: allConnectedSockets,
} as GetAllSocketClients);
});
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
);
});
// ==== DISCONNECT ====
socket.on("disconnect", () => {
console.log("user disconnected");
});
});
}
@@ -1,9 +1,10 @@
import { useAuthCheck, useServerCheck } from "../../controller/index";
import { $jwtToken } from "../../controller/recoil";
import Login from "../../pages/Login";
import NirvanaApi from "../../controller/nirvanaApi";
import { STORE_ITEMS } from "../../electron/constants";
import SkeletonLoader from "../loading/skeleton";
import { useAuthCheck } from "../../controller/index";
import { useEffect } from "react";
import { useRecoilState } from "recoil";
@@ -14,7 +15,17 @@ export default function ProtectedRoute({
}) {
const [jwtToken, setJwtToken] = useRecoilState($jwtToken);
const { isLoading, isError, isFetching, isSuccess, refetch } = useAuthCheck();
const {
isError: serverFailure,
isLoading: serverLoading,
isSuccess: isServerHealthy,
error,
} = useServerCheck();
console.log(error);
const { isLoading, isError, isFetching, isSuccess, refetch } =
useAuthCheck(isServerHealthy);
useEffect(() => {
// on load of this, if we already have jwt tokens in store,
@@ -49,6 +60,15 @@ export default function ProtectedRoute({
refetch();
}, [jwtToken]);
if (serverLoading)
return (
<span className="text-md text-gray-400 flex items-center justify-center flex-1 text-center p-5 h-screen">
Sorry...this is our bad. Our servers are loading. We are trying our best
to back up and running! :) <br /> Please contact me for urgent concerns:
arjunpatel@berkeley.edu
</span>
);
if (isLoading) {
return (
<div className="container h-screen w-screen flex flex-col justify-center mx-10">
+15 -1
View File
@@ -6,7 +6,20 @@ import { queryClient } from "../pages/nirvanaApp";
import { useRecoilValue } from "recoil";
// ====== QUERIES
export function useAuthCheck() {
/**
* ensure that the server is up
*/
export function useServerCheck() {
return useQuery("SERVER_CHECK", ApiCalls.serverCheck, {
retry: true,
refetchOnWindowFocus: false,
refetchInterval: 10000,
});
}
export function useAuthCheck(enabled: boolean = true) {
const jwtToken = useRecoilValue($jwtToken);
return useQuery("AUTH_CHECK", ApiCalls.authCheck, {
@@ -14,6 +27,7 @@ export function useAuthCheck() {
refetchOnWindowFocus: false,
refetchInterval: 10000,
enabled,
});
}
@@ -1,11 +1,13 @@
import React, { useContext } from "react";
import $ws from "./sockets";
import { $jwtToken } from "./recoil";
import MasterLineData from "@nirvana/core/models/masterLineData.model";
import SocketChannels from "@nirvana/core/sockets/channels";
import { User } from "@nirvana/core/models";
import { io } from "socket.io-client";
import toast from "react-hot-toast";
import { useEffect } from "react";
import { useRecoilValue } from "recoil";
import { useUserLines } from "./index";
interface ILineDataContext {
@@ -14,23 +16,50 @@ interface ILineDataContext {
relevantUsers: User[];
}
const lineDataContext = React.createContext<ILineDataContext>({
const LineDataContext = React.createContext<ILineDataContext>({
lines: [],
relevantUsers: [],
});
export function LineDataProvider() {
let $ws;
export function LineDataProvider({ children }) {
// persistent store of lines
const { data: basicUserLinesData } = useUserLines();
const jwtToken = useRecoilValue($jwtToken);
useEffect(() => {
$ws = io("http://localhost:5000", {
query: { token: jwtToken },
});
// client-side
$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");
});
}, []);
// connect through ws to enrich basic lines data
useEffect(() => {
// $ws.on(SocketChannels.CONNECT, );
console.log("got basic user lines data!!!");
// ?need to calculate diff and only do stuff then?
// $ws.on(SocketChannels.CONNECT, console.log());
$ws.on("test", () => console.log("test"));
}, [basicUserLinesData]);
// get updated associations with certain lines
return (
<LineDataContext.Provider value={{} as ILineDataContext}>
{children}
</LineDataContext.Provider>
);
}
export function useLineDataProvider() {
return useContext(lineDataContext);
return useContext(LineDataContext);
}
@@ -56,6 +56,10 @@ export default class NirvanaApi {
}
}
async function serverCheck(): Promise<void> {
return await NirvanaApi.fetch(`/`, "GET", false);
}
async function login(reqLoginTokens: {
accessToken: string;
idToken: string;
@@ -98,6 +102,7 @@ async function createLine(
}
export const ApiCalls = {
serverCheck,
login,
authCheck,
getUserDetails,
+1 -1
View File
@@ -1,12 +1,12 @@
import { QueryClient, QueryClientProvider } from "react-query";
import Login from "./Login";
import NirvanaRouter from "./router";
import ProtectedRoute from "../components/ProtectedRoute";
import { ReactQueryDevtools } from "react-query/devtools";
import { RecoilRoot } from "recoil";
import { Toaster } from "react-hot-toast";
import { configure } from "react-hotkeys";
import { io } from "socket.io-client";
import testConnection from "@nirvana/core";
testConnection();
+13 -9
View File
@@ -16,6 +16,7 @@ import React, { useState } from "react";
import { useCallback, useEffect, useMemo } from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { LineDataProvider } from "../../controller/lineDataProvider";
import LineDetailsTerminal from "../lineDetailsTerminal";
import NirvanaHeader from "../../components/header/index";
import NirvanaTerminal from "../terminal";
@@ -311,18 +312,21 @@ export default function NirvanaRouter() {
}, [setDesktopMode]);
return (
<div className="flex flex-col">
<NirvanaHeader onHeaderFocus={() => setDesktopMode("terminal")} />
<LineDataProvider>
<div className="flex flex-col">
<NirvanaHeader onHeaderFocus={() => setDesktopMode("terminal")} />
<div className="flex flex-row flex-1">
{(desktopMode === "terminal" || desktopMode === "terminalDetails") && (
<NirvanaTerminal allLines={testLines} />
)}
<div className="flex flex-row flex-1">
{(desktopMode === "terminal" ||
desktopMode === "terminalDetails") && (
<NirvanaTerminal allLines={testLines} />
)}
{desktopMode === "terminalDetails" && <LineDetailsTerminal />}
{desktopMode === "terminalDetails" && <LineDetailsTerminal />}
{desktopMode === "overlayOnly" && <Overlay />}
{desktopMode === "overlayOnly" && <Overlay />}
</div>
</div>
</div>
</LineDataProvider>
);
}
@@ -77,7 +77,7 @@ export default function NirvanaTerminal({
<Skeleton />
) : (
<>
{!userLinesRes?.data?.masterLines.length && (
{!userLinesRes?.data?.masterLines?.length && (
<span className="text-gray-300 text-sm my-5 text-center">
You have no lines! <br /> Create one to connect to your team
instantly.