Summary
Request-targeted broadcasts issued from a successful ConnectFn are silently dropped. This affects Request.Redirect, Request.Alert, reloads, JavaScript calls, and other helpers built on Jaws.Broadcast.
Class / severity
Correctness / lifecycle — Medium. Connection-initialization actions disappear without error on an otherwise healthy WebSocket.
Contract and valid usage
ConnectFn says it can interact with a Request before message processing starts. Request.Redirect imposes only the Jaws.Broadcast processing-loop requirement, and Request.ServeHTTP itself requires that loop to be running.
The callback below is synchronous, uses only its supplied Request, returns normally, and sends a valid relative redirect while Jaws.Serve is running.
Reproduction
func TestConnectFnRedirectDelivered(t *testing.T) {
jw, err := jaws.New()
if err != nil {
t.Fatal(err)
}
t.Cleanup(jw.Close)
go jw.Serve()
srv := httptest.NewServer(jw)
t.Cleanup(srv.Close)
initial := httptest.NewRequest(http.MethodGet, srv.URL+"/", nil)
initial.RemoteAddr = "127.0.0.1:1"
rq := jw.NewRequest(initial)
called := make(chan struct{})
rq.SetConnectFn(func(rq *jaws.Request) error {
rq.Redirect("/from-connect-fn")
close(called)
return nil
})
hdr := http.Header{}
hdr.Set("Origin", srv.URL)
ctx, cancel := context.WithTimeout(t.Context(), 3*time.Second)
defer cancel()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") +
"/jaws/" + rq.JawsKeyString()
conn, _, err := websocket.Dial(ctx, wsURL,
&websocket.DialOptions{HTTPHeader: hdr})
if err != nil {
t.Fatal(err)
}
defer conn.CloseNow()
<-called
// Repeated markers avoid assuming exactly when subscription finishes.
stop := make(chan struct{})
defer close(stop)
go func() {
ticker := time.NewTicker(10 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-stop:
return
case <-ticker.C:
jw.Alert("info", "post-connect-marker")
}
}
}()
_, data, err := conn.Read(ctx)
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(data, []byte("Redirect")) {
t.Fatalf("ConnectFn redirect was lost; first healthy frame: %s", data)
}
}
The observed first frame is the post-connect Alert marker; it contains no Redirect.
Root cause
Request.ServeHTTP calls rq.onConnect() before rq.Jaws.subscribe(...). Redirect synchronously reaches the Serve loop, which has no subscription for this Request and therefore drops the key-targeted message.
Impact
Login/navigation initialization, connection notifications, or session-driven updates initiated by ConnectFn silently fail for the connecting page.
Suggested fix
Establish a buffered subscription before calling onConnect, and unsubscribe it if the callback fails. Alternatively, queue callback-time Request broadcasts until the subscription is installed.
Summary
Request-targeted broadcasts issued from a successful
ConnectFnare silently dropped. This affectsRequest.Redirect,Request.Alert, reloads, JavaScript calls, and other helpers built onJaws.Broadcast.Class / severity
Correctness / lifecycle — Medium. Connection-initialization actions disappear without error on an otherwise healthy WebSocket.
Contract and valid usage
ConnectFnsays it can interact with aRequestbefore message processing starts.Request.Redirectimposes only theJaws.Broadcastprocessing-loop requirement, andRequest.ServeHTTPitself requires that loop to be running.The callback below is synchronous, uses only its supplied Request, returns normally, and sends a valid relative redirect while
Jaws.Serveis running.Reproduction
The observed first frame is the post-connect Alert marker; it contains no Redirect.
Root cause
Request.ServeHTTPcallsrq.onConnect()beforerq.Jaws.subscribe(...).Redirectsynchronously reaches the Serve loop, which has no subscription for this Request and therefore drops the key-targeted message.Impact
Login/navigation initialization, connection notifications, or session-driven updates initiated by
ConnectFnsilently fail for the connecting page.Suggested fix
Establish a buffered subscription before calling
onConnect, and unsubscribe it if the callback fails. Alternatively, queue callback-time Request broadcasts until the subscription is installed.