getting some socket stuff set up on client and server
This commit is contained in:
+55
-8
@@ -1,6 +1,7 @@
|
|||||||
import express, { Application, Request, Response } from "express";
|
import express, { Application, Request, Response } from "express";
|
||||||
|
|
||||||
import { NextFunction } from "express";
|
import { NextFunction } from "express";
|
||||||
|
import SocketChannels from "@nirvana/core/sockets/channels";
|
||||||
import { connectToDatabase } from "./services/database.service";
|
import { connectToDatabase } from "./services/database.service";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import getContactsRoutes from "./routes/contacts";
|
import getContactsRoutes from "./routes/contacts";
|
||||||
@@ -27,15 +28,61 @@ app.use("/api/search", getSearchRoutes());
|
|||||||
app.use("/api/conversations", getConversationRoutes());
|
app.use("/api/conversations", getConversationRoutes());
|
||||||
app.use("/api/contacts", getContactsRoutes());
|
app.use("/api/contacts", getContactsRoutes());
|
||||||
|
|
||||||
const server = new http.Server(app);
|
const PORT = 5000;
|
||||||
server.listen(5000, () =>
|
var server = app.listen(PORT, () => console.log("express running"));
|
||||||
console.log("Example app is listening on port 5000.")
|
connectToDatabase();
|
||||||
);
|
|
||||||
|
|
||||||
const io = require("socket.io")(server);
|
// socket IO stuff
|
||||||
|
|
||||||
io.on("connection", function (socket: any) {
|
const io = require("socket.io")(server, {
|
||||||
console.log("a user connected");
|
// todo: add protection
|
||||||
|
cors: {
|
||||||
|
origin: "*",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
connectToDatabase();
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==== 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, data: any) => {}
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.on(
|
||||||
|
SocketChannels.SEND_STARTED_SPEAKING,
|
||||||
|
(relationshipId: string, data: any) => {}
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.on(
|
||||||
|
SocketChannels.SEND_STOPPED_SPEAKING,
|
||||||
|
(relationshipId: string, data: any) => {}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==== DISCONNECT ====
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
console.log("user disconnected");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -8,5 +8,7 @@ export default class GetContactsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ContactDetails {
|
export class ContactDetails {
|
||||||
|
isSpeaking: boolean = false;
|
||||||
|
|
||||||
constructor(public otherUser: User, public relationship: Relationship) {}
|
constructor(public otherUser: User, public relationship: Relationship) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
enum SocketChannels {
|
||||||
|
JOIN_ROOM = "JOIN_ROOM",
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SocketChannels;
|
||||||
@@ -46,10 +46,10 @@
|
|||||||
[
|
[
|
||||||
"@electron-forge/plugin-webpack",
|
"@electron-forge/plugin-webpack",
|
||||||
{
|
{
|
||||||
"port": "3001",
|
"port": "3000",
|
||||||
"loggerPort": "9001",
|
"loggerPort": "9000",
|
||||||
"mainConfig": "./webpack.main.config.js",
|
"mainConfig": "./webpack.main.config.js",
|
||||||
"devContentSecurityPolicy": "connect-src 'self' http://localhost:5000 'unsafe-eval'",
|
"devContentSecurityPolicy": "connect-src 'self' http://localhost:5000 ws://localhost:5000 'unsafe-eval'",
|
||||||
"renderer": {
|
"renderer": {
|
||||||
"config": "./webpack.renderer.config.js",
|
"config": "./webpack.renderer.config.js",
|
||||||
"entryPoints": [
|
"entryPoints": [
|
||||||
@@ -112,6 +112,7 @@
|
|||||||
"react-hotkeys": "^2.0.0",
|
"react-hotkeys": "^2.0.0",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-query": "^3.34.16",
|
"react-query": "^3.34.16",
|
||||||
"recoil": "^0.6.1"
|
"recoil": "^0.6.1",
|
||||||
|
"socket.io-client": "^4.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { $authTokens, $searchQuery } from "./recoil";
|
import { $authTokens, $searchQuery } from "./recoil";
|
||||||
import axios, { AxiosResponse } from "axios";
|
import axios, { AxiosResponse } from "axios";
|
||||||
|
import { queryClient, socket } from "../nirvanaApp";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { useMutation, useQuery } from "react-query";
|
import { useMutation, useQuery } from "react-query";
|
||||||
|
|
||||||
import GetContactsResponse from "../../../core/responses/getContacts.response";
|
import GetContactsResponse from "../../../core/responses/getContacts.response";
|
||||||
@@ -7,10 +9,10 @@ import GetConversationDetailsResponse from "@nirvana/core/responses/getConversat
|
|||||||
import { ObjectId } from "mongodb";
|
import { ObjectId } from "mongodb";
|
||||||
import { RelationshipState } from "@nirvana/core/models/relationship.model";
|
import { RelationshipState } from "@nirvana/core/models/relationship.model";
|
||||||
import SearchResponse from "@nirvana/core/responses/search.response";
|
import SearchResponse from "@nirvana/core/responses/search.response";
|
||||||
|
import SocketChannels from "@nirvana/core/sockets/channels";
|
||||||
import UpdateRelationshipStateRequest from "../../../core/requests/updateRelationshipState.request";
|
import UpdateRelationshipStateRequest from "../../../core/requests/updateRelationshipState.request";
|
||||||
import { User } from "@nirvana/core/models";
|
import { User } from "@nirvana/core/models";
|
||||||
import { nirvanaApi } from "./nirvanaApi";
|
import { nirvanaApi } from "./nirvanaApi";
|
||||||
import { queryClient } from "../nirvanaApp";
|
|
||||||
import { useRecoilValue } from "recoil";
|
import { useRecoilValue } from "recoil";
|
||||||
|
|
||||||
// =========== API
|
// =========== API
|
||||||
@@ -133,13 +135,63 @@ export function useConversationDetails(otherUserGoogleId: string) {
|
|||||||
export function useGetAllContactBasicDetails() {
|
export function useGetAllContactBasicDetails() {
|
||||||
const authTokens = useRecoilValue($authTokens);
|
const authTokens = useRecoilValue($authTokens);
|
||||||
|
|
||||||
return useQuery(
|
// relationshipId's of the conversations where there is someone speaking
|
||||||
|
const [speakingRooms, setSpeakingRooms] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
socket.on(
|
||||||
|
SocketChannels.SEND_STARTED_SPEAKING,
|
||||||
|
(relationshipId: string) => {
|
||||||
|
setSpeakingRooms((prevSpeakingRooms) => [
|
||||||
|
...prevSpeakingRooms,
|
||||||
|
relationshipId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
socket.on(
|
||||||
|
SocketChannels.SEND_STOPPED_SPEAKING,
|
||||||
|
(relationshipId: string) => {
|
||||||
|
setSpeakingRooms((prevSpeakingRooms) =>
|
||||||
|
prevSpeakingRooms.filter(
|
||||||
|
(relationshipRoomId) => relationshipRoomId !== relationshipId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.removeAllListeners(SocketChannels.SEND_STARTED_SPEAKING);
|
||||||
|
socket.removeAllListeners(SocketChannels.SEND_STOPPED_SPEAKING);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const reactQueryRes = useQuery(
|
||||||
Querytypes.GET_CONTACTS_RELATIONSHIPS,
|
Querytypes.GET_CONTACTS_RELATIONSHIPS,
|
||||||
() => getContactsBasicDetails(authTokens.idToken),
|
() => getContactsBasicDetails(authTokens.idToken),
|
||||||
{
|
{
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// go through all conversations and mutate adding in data
|
||||||
|
// on whether or not someone is speaking or not
|
||||||
|
if (reactQueryRes.data) {
|
||||||
|
reactQueryRes.data.contactsDetails.map((contactDet) => {
|
||||||
|
// todo: join the right rooms based on the relevant contacts/conversations returned here
|
||||||
|
socket.emit(
|
||||||
|
SocketChannels.JOIN_ROOM,
|
||||||
|
contactDet.relationship._id.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
// if this contact/conversation is in the list of speaking ones, then change isSpeaking
|
||||||
|
if (speakingRooms.includes(contactDet.relationship._id.toString())) {
|
||||||
|
contactDet.isSpeaking = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return reactQueryRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========== MUTATIONS
|
// =========== MUTATIONS
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const createWindow = (): void => {
|
|||||||
browserWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
|
browserWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
|
||||||
|
|
||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
browserWindow.webContents.openDevTools({ mode: "detach" });
|
browserWindow.webContents.openDevTools({ mode: "right" });
|
||||||
};
|
};
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Login from "./pages/Login";
|
|||||||
import ProtectedRoute from "./components/ProtectedRoute";
|
import ProtectedRoute from "./components/ProtectedRoute";
|
||||||
import { ReactQueryDevtools } from "react-query/devtools";
|
import { ReactQueryDevtools } from "react-query/devtools";
|
||||||
import { RecoilRoot } from "recoil";
|
import { RecoilRoot } from "recoil";
|
||||||
|
import io from "socket.io-client";
|
||||||
import testConnection from "@nirvana/core";
|
import testConnection from "@nirvana/core";
|
||||||
|
|
||||||
testConnection();
|
testConnection();
|
||||||
@@ -12,6 +13,8 @@ testConnection();
|
|||||||
// Create a client
|
// Create a client
|
||||||
export const queryClient = new QueryClient();
|
export const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
export const socket = io("http://localhost:5000");
|
||||||
|
|
||||||
function NirvanaApp() {
|
function NirvanaApp() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Add, LinkRounded } from "@mui/icons-material";
|
|||||||
|
|
||||||
import { $selectedConversation } from "../../../controller/recoil";
|
import { $selectedConversation } from "../../../controller/recoil";
|
||||||
import { Avatar } from "antd";
|
import { Avatar } from "antd";
|
||||||
|
import { ContactDetails } from "@nirvana/core/responses/getContacts.response";
|
||||||
import { FaVolumeUp } from "react-icons/fa";
|
import { FaVolumeUp } from "react-icons/fa";
|
||||||
import SkeletonLoader from "../../../components/loading/skeleton";
|
import SkeletonLoader from "../../../components/loading/skeleton";
|
||||||
import UserAvatarWithStatus from "../../../components/User/userAvatarWithStatus";
|
import UserAvatarWithStatus from "../../../components/User/userAvatarWithStatus";
|
||||||
@@ -55,36 +56,46 @@ export default function Conversations() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoading ? <SkeletonLoader /> : null}
|
{isLoading ? (
|
||||||
<div className="flex flex-col m-5 p-4">
|
<SkeletonLoader />
|
||||||
<span className="flex flex-row justify-start items-center mb-2">
|
) : (
|
||||||
<span className="ml-2 tracking-wider text-slate-100 uppercase text-sm font-semibold">
|
<div className="flex flex-col m-5 p-4">
|
||||||
Inbox
|
<span className="flex flex-row justify-start items-center mb-2">
|
||||||
|
<span className="ml-2 tracking-wider text-slate-100 uppercase text-sm font-semibold">
|
||||||
|
Inbox
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
|
||||||
|
|
||||||
{contactDetailsListResponse?.contactsDetails.map((contactDetail) => {
|
{contactDetailsListResponse?.contactsDetails.map(
|
||||||
return (
|
(contactDetail: ContactDetails) => {
|
||||||
<div
|
return (
|
||||||
onClick={() => selectContact(contactDetail.otherUser.googleId)}
|
<div
|
||||||
className="flex flex-row items-center hover:bg-slate-600 group border-t border-t-slate-500
|
key={contactDetail.relationship._id.toString()}
|
||||||
|
onClick={() =>
|
||||||
|
selectContact(contactDetail.otherUser.googleId)
|
||||||
|
}
|
||||||
|
className="flex flex-row items-center hover:bg-slate-600 group border-t border-t-slate-500
|
||||||
py-4 px-2 cursor-pointer"
|
py-4 px-2 cursor-pointer"
|
||||||
>
|
>
|
||||||
<UserAvatarWithStatus user={contactDetail.otherUser} />
|
<UserAvatarWithStatus user={contactDetail.otherUser} />
|
||||||
|
|
||||||
<span className="text-white font-semibold ml-2">
|
<span className="text-white font-semibold ml-2">
|
||||||
{contactDetail.otherUser.name}
|
{contactDetail.otherUser.name}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span className="ml-2">
|
<span className="ml-2">
|
||||||
<UserStatusText status={contactDetail.otherUser.status} />
|
<UserStatusText status={contactDetail.otherUser.status} />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span className="ml-auto">speaking...</span>
|
{contactDetail.isSpeaking ? (
|
||||||
</div>
|
<span className="ml-auto">speaking...</span>
|
||||||
);
|
) : null}
|
||||||
})}
|
</div>
|
||||||
</div>
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import { Dimensions } from "../../../electron/constants";
|
|||||||
import { FaWindowClose } from "react-icons/fa";
|
import { FaWindowClose } from "react-icons/fa";
|
||||||
import { GlobalHotKeys } from "react-hotkeys";
|
import { GlobalHotKeys } from "react-hotkeys";
|
||||||
import { RelationshipState } from "@nirvana/core/models/relationship.model";
|
import { RelationshipState } from "@nirvana/core/models/relationship.model";
|
||||||
|
import SocketChannels from "@nirvana/core/sockets/channels";
|
||||||
import UpdateRelationshipStateRequest from "@nirvana/core/requests/updateRelationshipState.request";
|
import UpdateRelationshipStateRequest from "@nirvana/core/requests/updateRelationshipState.request";
|
||||||
import UserStatusText from "../../../components/User/userStatusText";
|
import UserStatusText from "../../../components/User/userStatusText";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { queryClient } from "../../../nirvanaApp";
|
import { queryClient } from "../../../nirvanaApp";
|
||||||
|
import { socket } from "../../../nirvanaApp";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useRecoilState } from "recoil";
|
import { useRecoilState } from "recoil";
|
||||||
|
|
||||||
@@ -126,6 +128,13 @@ export default function SelectedConversation() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// emit to room/conversation that someone started speaking
|
||||||
|
const socketStartedSpeaking = () => {
|
||||||
|
// send the room name and the update type
|
||||||
|
|
||||||
|
socket.emit(SocketChannels.SEND_STARTED_SPEAKING);
|
||||||
|
};
|
||||||
|
|
||||||
// hot keys for closing this window
|
// hot keys for closing this window
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setSelectedConvo(null);
|
setSelectedConvo(null);
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ export default function Login({ onReady }: { onReady: Function }) {
|
|||||||
if (authFailureCount >= 2) {
|
if (authFailureCount >= 2) {
|
||||||
logOut();
|
logOut();
|
||||||
} else {
|
} else {
|
||||||
|
// todo: comment all of this else when trying to test with two users/instances
|
||||||
|
|
||||||
// see if we have tokens in localstorage in which case we can continue on
|
// see if we have tokens in localstorage in which case we can continue on
|
||||||
window.electronAPI.store
|
window.electronAPI.store
|
||||||
.get(STORE_ITEMS.AUTH_TOKENS)
|
.get(STORE_ITEMS.AUTH_TOKENS)
|
||||||
|
|||||||
Reference in New Issue
Block a user