feat: emit peer_status (ICE state + candidate type) from daemon
Hooks pc.OnConnectionStateChange in WireDataChannel to emit peer_status events. On connected, calls pc.GetStats() to find the nominated candidate pair and report candidateType (host/srflx/relay) and remote address. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -94,6 +95,17 @@ func WireDataChannel(
|
||||
m.RemovePeer(peerID)
|
||||
pc.Close()
|
||||
})
|
||||
|
||||
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
|
||||
m.Emit(proto.IpcMessage{
|
||||
Type: proto.EvtPeerStatus,
|
||||
PeerID: &peerID,
|
||||
ConnState: state.String(),
|
||||
})
|
||||
if state == webrtc.PeerConnectionStateConnected {
|
||||
go emitICEStats(pc, peerID, m)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WireCandidateTrickle seals and forwards each ICE candidate via the anchor as it arrives.
|
||||
@@ -257,6 +269,42 @@ func dispatchPeerMessage(msg proto.PeerMessage, from proto.PeerID, m *Mesh) {
|
||||
}
|
||||
}
|
||||
|
||||
// emitICEStats reads the active (nominated) ICE candidate pair from pion's stats
|
||||
// and emits a peer_status event with the candidate type and remote address.
|
||||
func emitICEStats(pc *webrtc.PeerConnection, peerID proto.PeerID, m *Mesh) {
|
||||
stats := pc.GetStats()
|
||||
candidateType := "unknown"
|
||||
remoteAddress := ""
|
||||
|
||||
// Find the nominated candidate pair and its remote candidate.
|
||||
for _, s := range stats {
|
||||
pair, ok := s.(webrtc.ICECandidatePairStats)
|
||||
if !ok || !pair.Nominated {
|
||||
continue
|
||||
}
|
||||
// Look up remote candidate by its ID.
|
||||
if rc, ok2 := stats[pair.RemoteCandidateID]; ok2 {
|
||||
remote, ok3 := rc.(webrtc.ICECandidateStats)
|
||||
if ok3 {
|
||||
candidateType = remote.CandidateType.String()
|
||||
if remote.Port > 0 {
|
||||
remoteAddress = fmt.Sprintf("%s:%d", remote.IP, remote.Port)
|
||||
} else {
|
||||
remoteAddress = remote.IP
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
m.Emit(proto.IpcMessage{
|
||||
Type: proto.EvtPeerStatus,
|
||||
PeerID: &peerID,
|
||||
CandidateType: candidateType,
|
||||
RemoteAddress: remoteAddress,
|
||||
})
|
||||
}
|
||||
|
||||
// midOrRandom returns mid if non-empty, otherwise generates a random 16-byte hex string.
|
||||
// Ensures every stored message has a unique mid even from peers that don't send one.
|
||||
func midOrRandom(mid string) string {
|
||||
|
||||
@@ -228,6 +228,7 @@ const (
|
||||
EvtPeerConnected IpcMsgType = "peer_connected"
|
||||
EvtPeerDisconnected IpcMsgType = "peer_disconnected"
|
||||
EvtSessionReady IpcMsgType = "session_ready" // DataChannel open + hello verified
|
||||
EvtPeerStatus IpcMsgType = "peer_status" // ICE connection state + candidate type
|
||||
EvtIncomingFile IpcMsgType = "incoming_file"
|
||||
EvtFileProgress IpcMsgType = "file_progress"
|
||||
EvtStateSnapshot IpcMsgType = "state_snapshot"
|
||||
@@ -294,4 +295,8 @@ type IpcMessage struct {
|
||||
// export_identity / import_identity
|
||||
Passphrase string `json:"passphrase,omitempty"` // import only; never echoed back
|
||||
Backup string `json:"backup,omitempty"` // JSON backup blob
|
||||
// peer_status — ICE connection quality
|
||||
ConnState string `json:"conn_state,omitempty"` // pion PeerConnectionState string
|
||||
CandidateType string `json:"candidate_type,omitempty"` // host | srflx | relay | unknown
|
||||
RemoteAddress string `json:"remote_address,omitempty"` // remote IP:port of active candidate pair
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user