diff --git a/client/main.go b/client/main.go index f13f621..0c6d930 100644 --- a/client/main.go +++ b/client/main.go @@ -21,6 +21,7 @@ import ( "os" "runtime" "strings" + "sync" "time" "github.com/atotto/clipboard" @@ -179,7 +180,10 @@ func handleMessage(ev string, m Message) { // ── WebRTC ──────────────────────────────────────────────────────────────── -var incomingSignal = make(chan Message, 16) +var ( + incomingSignal = make(chan Message, 16) + rtcConnected = make(chan struct{}) +) func runRTCPeer(server string) { api := webrtc.NewAPI() @@ -200,8 +204,10 @@ func runRTCPeer(server string) { return } + var once sync.Once dc.OnOpen(func() { fmt.Fprintln(os.Stderr, "rtc: DataChannel OPEN — P2P live") + once.Do(func() { close(rtcConnected) }) }) dc.OnMessage(func(msg webrtc.DataChannelMessage) { text := string(msg.Data) @@ -225,7 +231,7 @@ func runRTCPeer(server string) { fmt.Fprintf(os.Stderr, "rtc: state=%s\n", s) }) - // Create offer + post via signaling bus + // Create offer + post once immediately offer, err := pc.CreateOffer(nil) if err != nil { log.Printf("rtc: create offer: %v", err) @@ -237,7 +243,24 @@ func runRTCPeer(server string) { } payload, _ := json.Marshal(SignalPayload{Kind: "offer", SDP: &offer}) send(server, myLabel, "", "signal", payload) - fmt.Fprintln(os.Stderr, "rtc: offer posted, waiting for answer...") + fmt.Fprintln(os.Stderr, "rtc: offer posted, will chirp every 5s until paired...") + + // "Chirp": re-post the offer every 5s until DataChannel opens. Catches + // late-joining browsers without needing server-side history of signals. + go func() { + t := time.NewTicker(5 * time.Second) + defer t.Stop() + for { + select { + case <-rtcConnected: + fmt.Fprintln(os.Stderr, "rtc: paired — chirping stopped") + return + case <-t.C: + p, _ := json.Marshal(SignalPayload{Kind: "offer", SDP: &offer}) + send(server, myLabel, "", "signal", p) + } + } + }() // Process incoming signals for {