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"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -94,6 +95,17 @@ func WireDataChannel(
|
|||||||
m.RemovePeer(peerID)
|
m.RemovePeer(peerID)
|
||||||
pc.Close()
|
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.
|
// 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.
|
// 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.
|
// Ensures every stored message has a unique mid even from peers that don't send one.
|
||||||
func midOrRandom(mid string) string {
|
func midOrRandom(mid string) string {
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ const (
|
|||||||
EvtPeerConnected IpcMsgType = "peer_connected"
|
EvtPeerConnected IpcMsgType = "peer_connected"
|
||||||
EvtPeerDisconnected IpcMsgType = "peer_disconnected"
|
EvtPeerDisconnected IpcMsgType = "peer_disconnected"
|
||||||
EvtSessionReady IpcMsgType = "session_ready" // DataChannel open + hello verified
|
EvtSessionReady IpcMsgType = "session_ready" // DataChannel open + hello verified
|
||||||
|
EvtPeerStatus IpcMsgType = "peer_status" // ICE connection state + candidate type
|
||||||
EvtIncomingFile IpcMsgType = "incoming_file"
|
EvtIncomingFile IpcMsgType = "incoming_file"
|
||||||
EvtFileProgress IpcMsgType = "file_progress"
|
EvtFileProgress IpcMsgType = "file_progress"
|
||||||
EvtStateSnapshot IpcMsgType = "state_snapshot"
|
EvtStateSnapshot IpcMsgType = "state_snapshot"
|
||||||
@@ -294,4 +295,8 @@ type IpcMessage struct {
|
|||||||
// export_identity / import_identity
|
// export_identity / import_identity
|
||||||
Passphrase string `json:"passphrase,omitempty"` // import only; never echoed back
|
Passphrase string `json:"passphrase,omitempty"` // import only; never echoed back
|
||||||
Backup string `json:"backup,omitempty"` // JSON backup blob
|
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