Skip to content

Commit e839551

Browse files
committed
fix: ready channel race on first client, implement Neko heartbeats
- Don't replace r.ready on first Start() call — only create a fresh channel on reconnection (when the previous one was already closed). Fixes race where first client would wait on an orphaned channel. - Parse heartbeat_interval from system/init and start a periodic client/heartbeat sender so Neko doesn't disconnect the relay. - Remove dead system/heartbeat handler (Neko heartbeats are client-initiated, not server-pushed). Made-with: Cursor
1 parent e440511 commit e839551

File tree

1 file changed

+35
-7
lines changed

1 file changed

+35
-7
lines changed

server/lib/webrtcscreen/relay.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,13 @@ func (r *Relay) ensureRunning() {
9494
// Start in a loop for automatic reconnection.
9595
func (r *Relay) Start(ctx context.Context) error {
9696
r.mu.Lock()
97-
r.ready = make(chan struct{})
97+
select {
98+
case <-r.ready:
99+
// Previous run closed this channel; create a fresh one for this run.
100+
r.ready = make(chan struct{})
101+
default:
102+
// Still open (first call or never closed), keep it.
103+
}
98104
r.mu.Unlock()
99105

100106
token, err := r.nekoLogin(ctx)
@@ -124,10 +130,34 @@ func (r *Relay) Start(ctx context.Context) error {
124130
r.mu.Unlock()
125131
}()
126132

127-
if err := r.waitForEvent(ctx, ws, "system/init"); err != nil {
133+
initPayload, err := r.waitForEvent(ctx, ws, "system/init")
134+
if err != nil {
128135
return fmt.Errorf("waiting for system/init: %w", err)
129136
}
130137

138+
var initData struct {
139+
HeartbeatInterval float64 `json:"heartbeat_interval"`
140+
}
141+
if initPayload != nil {
142+
_ = json.Unmarshal(initPayload, &initData)
143+
}
144+
if initData.HeartbeatInterval > 0 {
145+
go func() {
146+
ticker := time.NewTicker(time.Duration(initData.HeartbeatInterval * float64(time.Second)))
147+
defer ticker.Stop()
148+
for {
149+
select {
150+
case <-ctx.Done():
151+
return
152+
case <-ticker.C:
153+
if err := sendWSMsg(ctx, ws, "client/heartbeat", nil); err != nil {
154+
return
155+
}
156+
}
157+
}
158+
}()
159+
}
160+
131161
pc, err := webrtc.NewPeerConnection(webrtc.Configuration{})
132162
if err != nil {
133163
return fmt.Errorf("creating neko peer connection: %w", err)
@@ -502,15 +532,15 @@ func sendWSMsg(ctx context.Context, ws *cws.Conn, event string, payload json.Raw
502532
return ws.Write(ctx, cws.MessageText, data)
503533
}
504534

505-
func (r *Relay) waitForEvent(ctx context.Context, ws *cws.Conn, event string) error {
535+
func (r *Relay) waitForEvent(ctx context.Context, ws *cws.Conn, event string) (json.RawMessage, error) {
506536
for {
507537
_, data, err := ws.Read(ctx)
508538
if err != nil {
509-
return err
539+
return nil, err
510540
}
511541
var msg nekoMsg
512542
if json.Unmarshal(data, &msg) == nil && msg.Event == event {
513-
return nil
543+
return msg.Payload, nil
514544
}
515545
}
516546
}
@@ -545,8 +575,6 @@ func (r *Relay) nekoWSLoop(ctx context.Context, ws *cws.Conn, pc *webrtc.PeerCon
545575
continue
546576
}
547577
switch msg.Event {
548-
case "system/heartbeat":
549-
_ = sendWSMsg(ctx, ws, "client/heartbeat", nil)
550578
case "signal/candidate":
551579
var candidate webrtc.ICECandidateInit
552580
if json.Unmarshal(msg.Payload, &candidate) == nil {

0 commit comments

Comments
 (0)