The root cause of both bugs was that each peer had a single
peerConnection field used for both sending and receiving streams:
1. Starting a share while receiving another's stream failed because
sendOfferTo() saw the existing incoming PC and bailed out, so
the outgoing stream was never sent.
2. Stopping a share killed the other person's stream because
stopSharing() closed ALL peer connections, including the incoming
one used to receive their stream.
Fix by splitting into outgoingPC (created via sendOfferTo, used for
sending our stream) and incomingPC (created via handleOffer, used for
receiving their stream). ICE candidates now embed a direction flag so
the remote side routes them to the correct PC.
https://claude.ai/code/session_01ALSwS4S8EHiP81i2KMsb9Y
When a screenshare was started while both peers were already connected,
the viewer never received the video stream. This worked after a reload
because the peer_joined handler proactively sends an offer to the new
peer.
The root cause: startSharing() relied on a request_stream round-trip
(viewer sends request_stream, sharer responds with offer). Since
handleSignal is async but never awaited by ws.onmessage, this
round-trip could silently fail when promises rejected or messages
interleaved during the exchange.
Fix by having the sharer proactively send offers to all connected peers
in startSharing(), matching the existing peer_joined behavior. Also add
a fallback in ontrack for when event.streams is empty.
https://claude.ai/code/session_01ALSwS4S8EHiP81i2KMsb9Y
Modern browsers obfuscate local IP addresses with mDNS hostnames
(e.g. abcdef12-3456.local) for privacy. When mDNS resolution is
unavailable on the LAN, WebRTC ICE candidates cannot be resolved
and the peer connection fails.
This fix makes the signaling server extract each client's real IP
from the TCP connection and share it via Welcome/PeerJoined messages.
The client then replaces any .local mDNS addresses in SDP offers,
answers, and trickle ICE candidates with the peer's actual IP address,
enabling direct IP connectivity without relying on mDNS.
https://claude.ai/code/session_01EZrFdyAR35RLwRdHUA9zPJ