rtc works

This commit is contained in:
Kevin Puig
2025-09-28 08:24:26 -04:00
parent 5a5afcec32
commit afd2454674
3 changed files with 135 additions and 16 deletions

View File

@@ -89,6 +89,7 @@ model Channel {
model ChannelPin { model ChannelPin {
messageId String @unique messageId String @unique
channelId String @unique channelId String @unique
Message Message @relation(fields: [messageId], references: [id]) Message Message @relation(fields: [messageId], references: [id])
Channel Channel @relation(fields: [channelId], references: [id]) Channel Channel @relation(fields: [channelId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -113,7 +114,7 @@ model Message {
} }
model Reply { model Reply {
message Message @relation("MessageToReply", fields: [messageId], references: [id]) //message text message Message @relation("MessageToReply", fields: [messageId], references: [id]) //message text
messageId String @unique //message id of the reply messageId String @unique //message id of the reply
repliesTo Message @relation("ReplyToMessage", fields: [repliesToId], references: [id]) //message id that this message replies to repliesTo Message @relation("ReplyToMessage", fields: [repliesToId], references: [id]) //message id that this message replies to
repliesToId String @unique //replies to this message id repliesToId String @unique //replies to this message id

View File

@@ -13,7 +13,7 @@ const app = new Hono();
app.use( app.use(
"*", "*",
cors({ cors({
origin: "http://localhost:5173", origin: ["http://localhost:5173", "https://concord.kpuig.net"],
allowHeaders: [ allowHeaders: [
"Content-Type", "Content-Type",
"Authorization", "Authorization",
@@ -45,7 +45,7 @@ app.get("/scalar", Scalar({ url: "/openapi" }));
// initialize socket.io server // initialize socket.io server
const io = new Server({ const io = new Server({
cors: { cors: {
origin: "http://localhost:5173", origin: ["http://localhost:5173", "https://concord.kpuig.net"],
credentials: true, credentials: true,
}, },
}); });
@@ -67,10 +67,13 @@ export default {
if (url.pathname === "/socket.io/") { if (url.pathname === "/socket.io/") {
const response = await engine.handleRequest(req, server); const response = await engine.handleRequest(req, server);
// Add CORS headers explicitly // Add CORS headers explicitly
response.headers.set( const origin = req.headers.get("Origin");
"Access-Control-Allow-Origin", if (
"http://localhost:5173", origin &&
); ["http://localhost:5173", "https://concord.kpuig.net"].includes(origin)
) {
response.headers.set("Access-Control-Allow-Origin", origin);
}
response.headers.set("Access-Control-Allow-Credentials", "true"); response.headers.set("Access-Control-Allow-Credentials", "true");
return response; return response;
} else { } else {

View File

@@ -6,6 +6,30 @@ import { getCategoriesByInstance, getCategory, getChannel } from "../services/ch
// Change to Map of voiceChannelId to Map of userId to socket // Change to Map of voiceChannelId to Map of userId to socket
const voiceChannelMembers = new Map<string, Map<string, Socket>>(); const voiceChannelMembers = new Map<string, Map<string, Socket>>();
// Types for WebRTC messages
interface WebRTCOffer {
targetUserId: string;
sdp: RTCSessionDescriptionInit;
}
interface WebRTCAnswer {
targetUserId: string;
sdp: RTCSessionDescriptionInit;
}
interface WebRTCIceCandidate {
targetUserId: string;
candidate: RTCIceCandidateInit;
}
// Future ICE server configuration
// This can be expanded later to include TURN servers
interface IceServerConfig {
urls: string | string[];
username?: string;
credential?: string;
}
export function registerVoiceHandlers(io: Server) { export function registerVoiceHandlers(io: Server) {
io.on("connection", (socket: Socket) => { io.on("connection", (socket: Socket) => {
// Join voice channel // Join voice channel
@@ -61,7 +85,8 @@ export function registerVoiceHandlers(io: Server) {
socket.join(payload.voiceChannelId); socket.join(payload.voiceChannelId);
socket.emit("joined-voicechannel", { socket.emit("joined-voicechannel", {
voiceChannelId: payload.voiceChannelId, voiceChannelId: payload.voiceChannelId,
connectedUserIds: Array.from(channelMembers.keys()).filter(e => e !== payload.userId) connectedUserIds: Array.from(channelMembers.keys()).filter(e => e !== payload.userId),
iceServers: getIceServers() // Send ICE server config to client
}); });
socket.to(payload.voiceChannelId).emit("user-joined-voicechannel", { userId: payload.userId }); socket.to(payload.voiceChannelId).emit("user-joined-voicechannel", { userId: payload.userId });
@@ -103,7 +128,7 @@ export function registerVoiceHandlers(io: Server) {
socket.leave(payload.voiceChannelId); socket.leave(payload.voiceChannelId);
// Notify other users in the channel // Notify other users in the channel
socket.to(payload.voiceChannelId).emit("user-left-voicechannel", { io.to(payload.voiceChannelId).emit("user-left-voicechannel", {
userId: payload.userId, userId: payload.userId,
voiceChannelId: payload.voiceChannelId voiceChannelId: payload.voiceChannelId
}); });
@@ -139,7 +164,7 @@ export function registerVoiceHandlers(io: Server) {
channelMembers.delete(userId); channelMembers.delete(userId);
// Notify other members // Notify other members
socket.to(voiceChannelId).emit("user-left-voicechannel", { io.to(voiceChannelId).emit("user-left-voicechannel", {
userId, userId,
voiceChannelId, voiceChannelId,
reason: "disconnected" reason: "disconnected"
@@ -160,7 +185,7 @@ export function registerVoiceHandlers(io: Server) {
members.delete(memberId); members.delete(memberId);
// Notify other members // Notify other members
socket.to(channelId).emit("user-left-voicechannel", { io.to(channelId).emit("user-left-voicechannel", {
userId: memberId, userId: memberId,
voiceChannelId: channelId, voiceChannelId: channelId,
reason: "disconnected" reason: "disconnected"
@@ -178,17 +203,107 @@ export function registerVoiceHandlers(io: Server) {
// Handle WebRTC Offer // Handle WebRTC Offer
socket.on("webrtc-offer", async (data) => { socket.on("webrtc-offer", async (data) => {
// Implementation for handling WebRTC offer const payload = data as { targetUserId: string; sdp: any };
const senderUserId = socket.data.userId;
const voiceChannelId = socket.data.currentVoiceChannelId;
if (!payload || !senderUserId || !voiceChannelId) {
socket.emit("error-voicechannel", "Invalid WebRTC offer payload or sender not in voice channel");
return;
}
const channelMembers = voiceChannelMembers.get(voiceChannelId);
const targetSocket = channelMembers?.get(payload.targetUserId);
if (targetSocket) {
targetSocket.emit("webrtc-offer", {
senderUserId: senderUserId,
sdp: payload.sdp
});
} else {
socket.emit("error-voicechannel", "Target user not found in voice channel");
}
}); });
// Handle WebRTC Answer // Handle WebRTC Answer
socket.on("webrtc-answer", async (data) => { socket.on("webrtc-answer", (data: WebRTCAnswer) => {
// Implementation for handling WebRTC answer const senderUserId = socket.data.userId;
const voiceChannelId = socket.data.currentVoiceChannelId;
if (!data || !senderUserId || !voiceChannelId) {
socket.emit("error-voicechannel", "Invalid WebRTC answer data");
return;
}
// Forward the answer to the target user
const channelMembers = voiceChannelMembers.get(voiceChannelId);
const targetSocket = channelMembers?.get(data.targetUserId);
if (targetSocket) {
targetSocket.emit("webrtc-answer", {
senderUserId: senderUserId,
sdp: data.sdp
});
} else {
socket.emit("error-voicechannel", "Target user not found in voice channel");
}
}); });
// Handle ICE Candidates // Handle ICE Candidates
socket.on("webrtc-ice-candidate", async (data) => { socket.on("webrtc-ice-candidate", (data: WebRTCIceCandidate) => {
// Implementation for handling ICE candidates const senderUserId = socket.data.userId;
const voiceChannelId = socket.data.currentVoiceChannelId;
if (!data || !senderUserId || !voiceChannelId) {
socket.emit("error-voicechannel", "Invalid ICE candidate data");
return;
}
// Forward the ICE candidate to the target user
const channelMembers = voiceChannelMembers.get(voiceChannelId);
const targetSocket = channelMembers?.get(data.targetUserId);
if (targetSocket) {
targetSocket.emit("webrtc-ice-candidate", {
senderUserId: senderUserId,
candidate: data.candidate
});
} else {
socket.emit("error-voicechannel", "Target user not found in voice channel");
}
}); });
}); });
} }
/**
* Get the current ICE server configuration.
* This function returns STUN servers and includes TURN server credentials
* if they are available in the environment variables.
*/
function getIceServers(): IceServerConfig[] {
const iceServers: IceServerConfig[] = [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
];
// Add own STUN server if configured
const stunServerUrl = process.env.STUN_SERVER_URL;
if (stunServerUrl) {
iceServers.push({ urls: stunServerUrl });
}
// Add TURN server if configured in environment variables
const turnServerUrl = process.env.TURN_SERVER_URL;
const turnUsername = process.env.TURN_SERVER_USERNAME;
const turnCredential = process.env.TURN_SERVER_CREDENTIAL;
if (turnServerUrl && turnUsername && turnCredential) {
iceServers.push({
urls: turnServerUrl,
username: turnUsername,
credential: turnCredential,
});
}
return iceServers;
}