rtc works
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user