Skip to content

Commit 8260918

Browse files
frahlgclaude
andauthored
fix(mir-signal): forward /registry in --webroot mode (404 in production) (#49)
B2.1 registered /registry on signal.Server.Handler(), but the production --webroot wrapper (withStatic) has its OWN hardcoded signalPaths map and /registry was missing from it — so the live relay 404'd /registry into the static file server. Tests used Server.Handler() directly and never saw the wrapper. Add /registry to signalPaths (and to sw.js SIGNALING so the SW doesn't cache it), plus TestWithStaticForwardsSignalingPaths which pins the production wrapper (it fails with the exact 404 when the route is dropped). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ebe72f5 commit 8260918

3 files changed

Lines changed: 47 additions & 2 deletions

File tree

go/cmd/mir-signal/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,12 @@ func newHTTPServer(addr string, handler http.Handler) *http.Server {
144144
func withStatic(sig http.Handler, dir string) http.Handler {
145145
fs := http.FileServer(http.Dir(dir))
146146
indexPath := filepath.Join(dir, "index.html")
147-
signalPaths := map[string]bool{"/agent/signal": true, "/attach": true, "/pair": true, "/turn-credentials": true, "/healthz": true}
147+
// Every path the signal server owns must be forwarded here, or --webroot mode
148+
// 404s it into the static file server. This list MUST stay in sync with the
149+
// routes registered in signal.Server.Handler(); main_test.go's
150+
// TestWithStaticForwardsSignalingPaths guards against drift (it caught /registry
151+
// going missing in production).
152+
signalPaths := map[string]bool{"/agent/signal": true, "/attach": true, "/pair": true, "/turn-credentials": true, "/healthz": true, "/registry": true}
148153
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
149154
if signalPaths[r.URL.Path] {
150155
sig.ServeHTTP(w, r)

go/cmd/mir-signal/main_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,55 @@
11
package main
22

33
import (
4+
"io"
45
"net/http"
56
"net/http/httptest"
67
"os"
78
"path/filepath"
89
"strings"
910
"testing"
1011
"time"
12+
13+
"github.com/srcful/terminal-relay/go/internal/signal"
1114
)
1215

16+
// TestWithStaticForwardsSignalingPaths guards the production --webroot wiring: every
17+
// path the signal server owns must be forwarded to it, not 404'd into the static
18+
// file server. /registry shipped registered in signal.Server.Handler() but MISSING
19+
// from withStatic's signalPaths map, so it 404'd in production (tests that used
20+
// Server.Handler() directly never saw the wrapper). This pins the wrapper itself.
21+
func TestWithStaticForwardsSignalingPaths(t *testing.T) {
22+
dir := t.TempDir()
23+
if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte("<html>spa</html>"), 0o644); err != nil {
24+
t.Fatal(err)
25+
}
26+
ts := httptest.NewServer(withStatic(signal.New().Handler(), dir))
27+
defer ts.Close()
28+
29+
// /registry MUST be forwarded to the signal server (handleRegistry returns a JSON
30+
// array for a wallet query) — not 404'd into the static FS.
31+
resp, err := http.Get(ts.URL + "/registry?wallet=test")
32+
if err != nil {
33+
t.Fatal(err)
34+
}
35+
body, _ := io.ReadAll(resp.Body)
36+
resp.Body.Close()
37+
if resp.StatusCode != http.StatusOK || !strings.HasPrefix(strings.TrimSpace(string(body)), "[") {
38+
t.Fatalf("/registry via withStatic = %d %q; want 200 JSON array (forwarded to signal, not static 404)", resp.StatusCode, body)
39+
}
40+
41+
// /healthz is forwarded too.
42+
if r, err := http.Get(ts.URL + "/healthz"); err != nil || r.StatusCode != http.StatusOK {
43+
t.Fatalf("/healthz via withStatic not forwarded (err=%v)", err)
44+
}
45+
46+
// A missing static asset still 404s via the FS — proves we aren't trivially
47+
// forwarding everything to the signal server.
48+
if r, err := http.Get(ts.URL + "/vendor/missing-asset.js"); err != nil || r.StatusCode != http.StatusNotFound {
49+
t.Fatalf("a missing static asset should 404 via the FS (got err=%v)", err)
50+
}
51+
}
52+
1353
func TestNewHTTPServerSetsTimeouts(t *testing.T) {
1454
handler := http.NewServeMux()
1555
srv := newHTTPServer(":0", handler)

web/sw.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const SHELL = [
6666
// Live conversations with the relay — never cached, never intercepted.
6767
// (WebSocket upgrades bypass the fetch handler anyway; this covers the plain
6868
// HTTP ones like /turn-credentials and keeps the list in one place.)
69-
const SIGNALING = ['/agent/signal', '/attach', '/pair', '/turn-credentials', '/healthz'];
69+
const SIGNALING = ['/agent/signal', '/attach', '/pair', '/turn-credentials', '/healthz', '/registry'];
7070

7171
self.addEventListener('install', (event) => {
7272
event.waitUntil(

0 commit comments

Comments
 (0)