Skip to content

Commit 8b83646

Browse files
authored
Merge pull request #3 from martian56/fix-con-issues
Fixes ICE candidate issue
2 parents 09b1b54 + 43a18a6 commit 8b83646

File tree

4 files changed

+106
-47
lines changed

4 files changed

+106
-47
lines changed

backend/config.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import os
22
import re
3-
from pydantic_settings import BaseSettings
3+
from pydantic_settings import BaseSettings, SettingsConfigDict
44
from dotenv import load_dotenv
55

66
load_dotenv()
77

88

99
class Settings(BaseSettings):
10+
model_config = SettingsConfigDict(
11+
env_file=".env",
12+
case_sensitive=False,
13+
extra="ignore" # Ignore extra environment variables like CORS_ORIGINS
14+
)
15+
1016
# Database
1117
database_url: str = os.getenv("DATABASE_URL", "postgresql://localhost/videocall")
1218

@@ -18,20 +24,23 @@ def async_database_url(self) -> str:
1824
# Security
1925
secret_key: str = os.getenv("SECRET_KEY", "dev-secret-key-change-in-production")
2026

21-
# CORS
22-
cors_origins: list[str] = [
23-
origin.strip()
24-
for origin in os.getenv("CORS_ORIGINS", "http://localhost:5173,https://linkup.ufazien.com").split(",")
25-
if origin.strip()
26-
]
27+
@property
28+
def cors_origins_str(self) -> str:
29+
"""Get CORS origins as string from environment"""
30+
return os.getenv("CORS_ORIGINS", "http://localhost:5173,https://linkup.ufazien.com")
31+
32+
@property
33+
def cors_origins(self) -> list[str]:
34+
"""Get CORS origins as a list"""
35+
return [
36+
origin.strip()
37+
for origin in self.cors_origins_str.split(",")
38+
if origin.strip()
39+
]
2740

2841
# Application
29-
app_name: str = "VideoCall"
42+
app_name: str = "LinkUp"
3043
debug: bool = os.getenv("DEBUG", "False").lower() == "true"
31-
32-
class Config:
33-
env_file = ".env"
34-
case_sensitive = False
3544

3645

3746
settings = Settings()

frontend/src/hooks/useWebRTC.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,12 @@ export const useWebRTC = (options?: UseWebRTCOptions) => {
4141

4242
// Add local stream tracks
4343
if (localStreamRef.current) {
44+
console.log('Adding local tracks to peer connection for:', clientId);
4445
localStreamRef.current.getTracks().forEach((track) => {
4546
pc.addTrack(track, localStreamRef.current!);
4647
});
48+
} else {
49+
console.warn('No local stream available when creating peer connection for:', clientId);
4750
}
4851

4952
// Handle remote stream
@@ -96,15 +99,35 @@ export const useWebRTC = (options?: UseWebRTCOptions) => {
9699
}, [createPeerConnection]);
97100

98101
const handleOffer = useCallback(async (clientId: string, offer: RTCSessionDescriptionInit): Promise<RTCSessionDescriptionInit | null> => {
99-
const pc = createPeerConnection(clientId);
102+
// Check if we already have a peer connection for this client
103+
let pc = peerConnectionsRef.current.get(clientId);
104+
if (!pc) {
105+
console.log('Creating new peer connection for offer from:', clientId);
106+
pc = createPeerConnection(clientId);
107+
} else {
108+
console.log('Reusing existing peer connection for offer from:', clientId);
109+
// If remote description is already set, we might be handling a duplicate offer
110+
if (pc.remoteDescription) {
111+
console.warn('Remote description already set for:', clientId, 'current state:', pc.signalingState);
112+
// If we're in a state where we can set it again, continue, otherwise return
113+
if (pc.signalingState === 'stable') {
114+
// We can set it again
115+
} else {
116+
console.error('Cannot set remote description, signaling state:', pc.signalingState);
117+
return null;
118+
}
119+
}
120+
}
100121

101122
try {
102123
await pc.setRemoteDescription(new RTCSessionDescription(offer));
124+
console.log('Set remote description for:', clientId, 'signaling state:', pc.signalingState);
103125
const answer = await pc.createAnswer();
104126
await pc.setLocalDescription(answer);
127+
console.log('Created and set local answer for:', clientId);
105128
return answer;
106129
} catch (error) {
107-
console.error('Error handling offer:', error);
130+
console.error('Error handling offer:', error, 'signaling state:', pc?.signalingState);
108131
return null;
109132
}
110133
}, [createPeerConnection]);

frontend/src/hooks/useWebSocket.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ export const useWebSocket = (
3232
const [error, setError] = useState<Error | null>(null);
3333
const wsRef = useRef<WebSocket | null>(null);
3434
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
35+
const onMessageRef = useRef(onMessage);
36+
37+
// Keep the ref updated without causing reconnections
38+
useEffect(() => {
39+
onMessageRef.current = onMessage;
40+
}, [onMessage]);
3541

3642
const connect = useCallback(() => {
3743
try {
@@ -51,7 +57,8 @@ export const useWebSocket = (
5157
try {
5258
const message: WSMessage = JSON.parse(event.data);
5359
setLastMessage(message);
54-
onMessage?.(message);
60+
// Use ref to avoid dependency issues
61+
onMessageRef.current?.(message);
5562
} catch (err) {
5663
console.error('Error parsing WebSocket message:', err);
5764
}
@@ -77,7 +84,7 @@ export const useWebSocket = (
7784
console.error('Error creating WebSocket:', err);
7885
setError(err as Error);
7986
}
80-
}, [meetingCode, clientId, onMessage]);
87+
}, [meetingCode, clientId]);
8188

8289
useEffect(() => {
8390
connect();

frontend/src/pages/MeetingRoom.tsx

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,14 @@ export default function MeetingRoom() {
5151
} = useWebRTC({
5252
onIceCandidate: (clientId: string, candidate: RTCIceCandidate) => {
5353
if (sendMessageRef.current) {
54+
console.log('Sending ICE candidate to:', clientId);
5455
sendMessageRef.current({
5556
type: WSMessageType.ICE_CANDIDATE,
5657
target: clientId,
5758
data: candidate.toJSON(),
5859
});
60+
} else {
61+
console.warn('sendMessageRef is null, cannot send ICE candidate to:', clientId);
5962
}
6063
},
6164
});
@@ -78,22 +81,38 @@ export default function MeetingRoom() {
7881

7982
// WebSocket message handler
8083
const handleWebSocketMessage = useCallback((message: WSMessage) => {
81-
console.log('Received WebSocket message:', message.type);
84+
console.log('Received WebSocket message:', message.type, 'Full message:', message);
8285

8386
switch (message.type) {
8487
case WSMessageType.USER_JOINED:
8588
if (message.clientId && message.clientId !== clientId) {
86-
setOtherParticipants((prev) => [...prev, message.clientId!]);
87-
// Create offer to new participant
88-
createOffer(message.clientId).then((offer) => {
89-
if (offer && sendMessageRef.current) {
90-
sendMessageRef.current({
91-
type: WSMessageType.OFFER,
92-
target: message.clientId,
93-
data: offer,
94-
});
89+
console.log('User joined:', message.clientId);
90+
setOtherParticipants((prev) => {
91+
if (!prev.includes(message.clientId!)) {
92+
return [...prev, message.clientId!];
9593
}
94+
return prev;
9695
});
96+
// Create offer to new participant only if we don't already have a connection
97+
if (!hasPeerConnection(message.clientId) && sendMessageRef.current) {
98+
console.log('Creating offer to:', message.clientId);
99+
createOffer(message.clientId).then((offer) => {
100+
if (offer && sendMessageRef.current) {
101+
console.log('Sending offer to:', message.clientId);
102+
sendMessageRef.current({
103+
type: WSMessageType.OFFER,
104+
target: message.clientId,
105+
data: offer,
106+
});
107+
} else {
108+
console.error('Failed to create offer or sendMessageRef is null');
109+
}
110+
}).catch((error) => {
111+
console.error('Error creating offer:', error);
112+
});
113+
} else {
114+
console.log('Already have peer connection with:', message.clientId);
115+
}
97116
}
98117
break;
99118

@@ -114,52 +133,49 @@ export default function MeetingRoom() {
114133
const others = message.participants.filter((id: string) => id !== clientId);
115134
setOtherParticipants(others);
116135

117-
// Create offers to existing participants when we first join
118-
// Use hasPeerConnection to avoid creating duplicate offers
119-
if (!isInitialized && others.length > 0) {
120-
others.forEach((otherId: string) => {
121-
// Only create offer if we don't already have a peer connection
122-
// This prevents duplicate offers if we already received one from this peer
123-
if (!hasPeerConnection(otherId) && sendMessageRef.current) {
124-
createOffer(otherId).then((offer) => {
125-
if (offer && sendMessageRef.current) {
126-
sendMessageRef.current({
127-
type: WSMessageType.OFFER,
128-
target: otherId,
129-
data: offer,
130-
});
131-
}
132-
});
133-
}
134-
});
135-
}
136+
// When we first join, we receive the list of existing participants
137+
// We should wait for existing participants to send us offers (via USER_JOINED on their side)
138+
// Don't create offers here - let existing participants initiate via USER_JOINED
136139
setIsInitialized(true);
137140
}
138141
break;
139142

140143
case WSMessageType.OFFER:
141144
if (message.from && message.data) {
145+
console.log('Received offer from:', message.from, 'localStream ready:', !!localStream);
146+
// Handle offer - the useWebRTC hook will check if local stream is ready
142147
handleOffer(message.from, message.data).then((answer) => {
143148
if (answer && sendMessageRef.current) {
149+
console.log('Sending answer to:', message.from);
144150
sendMessageRef.current({
145151
type: WSMessageType.ANSWER,
146152
target: message.from,
147153
data: answer,
148154
});
155+
} else {
156+
console.error('Failed to create answer or sendMessageRef is null');
149157
}
158+
}).catch((error) => {
159+
console.error('Error handling offer:', error);
150160
});
151161
}
152162
break;
153163

154164
case WSMessageType.ANSWER:
155165
if (message.from && message.data) {
156-
handleAnswer(message.from, message.data);
166+
console.log('Received answer from:', message.from);
167+
handleAnswer(message.from, message.data).catch((error) => {
168+
console.error('Error handling answer:', error);
169+
});
157170
}
158171
break;
159172

160173
case WSMessageType.ICE_CANDIDATE:
161174
if (message.from && message.data) {
162-
handleRemoteIceCandidate(message.from, message.data);
175+
console.log('Received ICE candidate from:', message.from);
176+
handleRemoteIceCandidate(message.from, message.data).catch((error) => {
177+
console.error('Error handling ICE candidate:', error);
178+
});
163179
}
164180
break;
165181

@@ -208,6 +224,10 @@ export default function MeetingRoom() {
208224
});
209225
}
210226
break;
227+
228+
default:
229+
console.warn('Unhandled WebSocket message type:', message.type, message);
230+
break;
211231
}
212232
}, [clientId, createOffer, handleOffer, handleAnswer, handleRemoteIceCandidate, removePeer, hasPeerConnection, isInitialized]);
213233

0 commit comments

Comments
 (0)