Files
nirvana-v3/packages/api/sockets/index.ts

356 lines
12 KiB
TypeScript

import {
ConnectToLineRequest,
RtcAnswerRequest,
RtcCallRequest,
RtcNewUserResponse,
RtcReceiveAnswerResponse,
ServerRequestChannels,
ServerResponseChannels,
SomeoneConnectedResponse,
SomeoneTunedResponse,
SomeoneUntunedFromLineResponse,
StartBroadcastingRequest,
StopBroadcastingRequest,
TuneToLineRequest,
UntuneFromLineRequest,
UserStartedBroadcastingResponse,
UserStoppedBroadcastingResponse,
} from "@nirvana/core/sockets/channels";
import GetAllSocketClients from "@nirvana/core/sockets/getAllActiveSocketClients";
import { JwtClaims } from "../middleware/auth";
import { LineMemberState } from "@nirvana/core/models/line.model";
import { LineService } from "../services/line.service";
import ReceiveSignal from "@nirvana/core/sockets/receiveSignal";
import SendSignal from "@nirvana/core/sockets/sendSignal";
import { UserService } from "../services/user.service";
import { UserStatus } from "@nirvana/core/models/user.model";
import { client } from "../services/database.service";
import { loadConfig } from "../config";
const jwt = require("jsonwebtoken");
const config = loadConfig();
// NOTE: client socket connections should never have to deal with socketIds
const socketIdsToUserIds: {
[socketId: string]: string;
} = {};
const userIdsToSocketIds: {
[userId: string]: string;
} = {};
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;
socketIdsToUserIds[socket.id] = userInfo.userId.toString();
userIdsToSocketIds[userInfo.userId.toString()] = socket.id;
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?
/** CONNECT | User wants to subscribe to live emissions of a line */
socket.on(
ServerRequestChannels.CONNECT_TO_LINE,
(req: ConnectToLineRequest) => {
// add this user to the room
console.log(
`${socket.id} user CONNECTED room for line ${Object.keys(
socket.rooms
)}`
);
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
)
);
}
);
/**
* 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}`
);
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
)
);
}
);
/**
* Notify all connected users when someone UNTUNES from a room
* ?might not be needed, all users' memory of tuned in users is irrelevant? don't need real time? but UI will show # of users tuned in?
*/
socket.on(
ServerRequestChannels.UNTUNE_FROM_LINE,
async (req: UntuneFromLineRequest) => {
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]
);
// 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
)
);
}
);
// TODO: use same pattern as tuning and untuning and send updated fresh list of current broadcasters but using another namespace/room for broadcasters in a line
/** BROADCAST UPDATE | tell all connected, not just tuned into, that there is an update to someone broadcasting */
socket.on(
ServerRequestChannels.BROADCAST_TO_LINE,
(req: StartBroadcastingRequest) => {
const roomName = `connectedLine:${req.lineId}`;
io.in(roomName).emit(
ServerResponseChannels.SOMEONE_STARTED_BROADCASTING,
new UserStartedBroadcastingResponse(req.lineId, userInfo.userId)
);
}
);
socket.on(
ServerRequestChannels.STOP_BROADCAST_TO_LINE,
(req: StopBroadcastingRequest) => {
const roomName = `connectedLine:${req.lineId}`;
io.in(roomName).emit(
ServerResponseChannels.SOMEONE_STOPPED_BROADCASTING,
new UserStoppedBroadcastingResponse(req.lineId, userInfo.userId)
);
}
);
// 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];
io.to(userSocketId).emit(
`${ServerResponseChannels.RTC_NEW_USER_JOINED_RESPONSE_PREFIX}:${req.lineId}`,
new RtcNewUserResponse(userInfo.userId, req.simplePeerSignal)
);
}
);
socket.on(
ServerRequestChannels.RTC_ANSWER_REQUEST,
(req: RtcAnswerRequest) => {
const userSocketId = userIdsToSocketIds[req.userIdToCall];
io.to(userSocketId).emit(
`${ServerResponseChannels.RTC_RECEIVING_ANSWER_RESPONSE_PREFIX}:${req.lineId}`,
new RtcReceiveAnswerResponse(userInfo.userId, req.simplePeerSignal)
);
}
);
// 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 the tuned in folks the new list of
socket.on("disconnecting", (reason: any) => {
console.log(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));
}
}
}
});
// ==== DISCONNECT ====
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
});
});
}