-
-
Notifications
You must be signed in to change notification settings - Fork 15
Fix timing issue: start WebRTC connection when clicking peer icon #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -149,9 +149,17 @@ function addPeer(id, name) { | |||||||||||||||||||||||||||||||||
| <div class="peer-name">${name}</div> | ||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Click to send file | ||||||||||||||||||||||||||||||||||
| // Click to send file - start connection immediately when clicking on peer | ||||||||||||||||||||||||||||||||||
| el.addEventListener('click', () => { | ||||||||||||||||||||||||||||||||||
| currentTransferTarget = id; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Start WebRTC connection immediately when user clicks on peer | ||||||||||||||||||||||||||||||||||
| // This gives time for connection to establish before file is selected | ||||||||||||||||||||||||||||||||||
| const peer = peers.get(id); | ||||||||||||||||||||||||||||||||||
| if (!peer.connection || peer.connection.connectionState !== 'connected') { | ||||||||||||||||||||||||||||||||||
| startConnection(id); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| fileInput.click(); | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
@@ -180,6 +188,16 @@ function getOrCreateConnection(peerId) { | |||||||||||||||||||||||||||||||||
| if (!peer.connection) { | ||||||||||||||||||||||||||||||||||
| const pc = new RTCPeerConnection(rtcConfig); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Track connection state | ||||||||||||||||||||||||||||||||||
| pc.onconnectionstatechange = () => { | ||||||||||||||||||||||||||||||||||
| console.log(`Connection state with ${peer.name}: ${pc.connectionState}`); | ||||||||||||||||||||||||||||||||||
| if (pc.connectionState === 'connected') { | ||||||||||||||||||||||||||||||||||
| showToast(`Connected to ${peer.name}`, 'success'); | ||||||||||||||||||||||||||||||||||
| } else if (pc.connectionState === 'failed' || pc.connectionState === 'disconnected') { | ||||||||||||||||||||||||||||||||||
| showToast(`Connection lost with ${peer.name}`, 'error'); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Output ICE candidates to signaling server | ||||||||||||||||||||||||||||||||||
| pc.onicecandidate = (e) => { | ||||||||||||||||||||||||||||||||||
| if (e.candidate) { | ||||||||||||||||||||||||||||||||||
|
|
@@ -263,20 +281,78 @@ fileInput.addEventListener('change', async (e) => { | |||||||||||||||||||||||||||||||||
| const file = e.target.files[0]; | ||||||||||||||||||||||||||||||||||
| if (!file || !currentTransferTarget) return; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // If connection isn't established, establish it first | ||||||||||||||||||||||||||||||||||
| const peer = peers.get(currentTransferTarget); | ||||||||||||||||||||||||||||||||||
| if (!peer.connection || peer.connection.connectionState !== 'connected') { | ||||||||||||||||||||||||||||||||||
| await startConnection(currentTransferTarget); | ||||||||||||||||||||||||||||||||||
| // Wait briefly for connection (in reality, should listen for connection state change) | ||||||||||||||||||||||||||||||||||
| setTimeout(() => sendFileHeader(currentTransferTarget, file), 1000); | ||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Check if connection exists and is connected | ||||||||||||||||||||||||||||||||||
| if (peer.connection && peer.connection.connectionState === 'connected') { | ||||||||||||||||||||||||||||||||||
| sendFileHeader(currentTransferTarget, file); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
284
to
288
|
||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| // Connection is being established (or was just started on peer click) | ||||||||||||||||||||||||||||||||||
| // Wait for connection to be ready with exponential backoff | ||||||||||||||||||||||||||||||||||
| let attempts = 0; | ||||||||||||||||||||||||||||||||||
| const maxAttempts = 10; | ||||||||||||||||||||||||||||||||||
| const checkConnection = () => { | ||||||||||||||||||||||||||||||||||
| attempts++; | ||||||||||||||||||||||||||||||||||
| if (peer.connection && peer.connection.connectionState === 'connected') { | ||||||||||||||||||||||||||||||||||
| sendFileHeader(currentTransferTarget, file); | ||||||||||||||||||||||||||||||||||
| } else if (attempts < maxAttempts) { | ||||||||||||||||||||||||||||||||||
| // Exponential backoff: 100ms, 200ms, 400ms, etc. | ||||||||||||||||||||||||||||||||||
| setTimeout(checkConnection, 100 * Math.pow(2, attempts)); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+294
to
+300
|
||||||||||||||||||||||||||||||||||
| const checkConnection = () => { | |
| attempts++; | |
| if (peer.connection && peer.connection.connectionState === 'connected') { | |
| sendFileHeader(currentTransferTarget, file); | |
| } else if (attempts < maxAttempts) { | |
| // Exponential backoff: 100ms, 200ms, 400ms, etc. | |
| setTimeout(checkConnection, 100 * Math.pow(2, attempts)); | |
| const maxDelay = 5000; // cap per-attempt delay to 5 seconds | |
| const checkConnection = () => { | |
| attempts++; | |
| if (peer.connection && peer.connection.connectionState === 'connected') { | |
| sendFileHeader(currentTransferTarget, file); | |
| } else if (attempts < maxAttempts) { | |
| // Exponential backoff: 100ms, 200ms, 400ms, etc., capped at maxDelay | |
| const delay = Math.min(100 * Math.pow(2, attempts - 1), maxDelay); | |
| setTimeout(checkConnection, delay); |
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
startConnection is async but is invoked here without await/.catch(...). If createOffer/setLocalDescription fails, this can surface as an unhandled promise rejection and you won’t be able to notify the user. Consider handling the returned promise (even if you don’t block UI) and showing an error toast on failure.
| startConnection(peerId); | |
| startConnection(peerId).catch((err) => { | |
| console.error('Failed to start connection for peer', peerId, err); | |
| showToast('Connection failed. Try again.', 'error'); | |
| }); |
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In sendFileHeader, the !peer and !peer.dataChannel cases are handled together, but the function still calls startConnection(peerId) even when peer is null/undefined. That will cause getOrCreateConnection to return null and then pc.createDataChannel(...) will throw. Split the condition so that a missing peer returns early without trying to connect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clicking a peer can trigger
startConnection(id)repeatedly (e.g., multiple clicks whileconnectionStateis stillnew/connecting).startConnectionalways creates a new DataChannel and sends a new offer, which can lead to multiple channels/offers in flight and negotiation errors. Consider tracking a per-peer “connecting/negotiating” flag (or checkingpc.signalingState/ existingpeer.dataChannel) sostartConnectionis only invoked once until it either connects or fails.