Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion go/cmd/mir-signal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,12 @@ func newHTTPServer(addr string, handler http.Handler) *http.Server {
func withStatic(sig http.Handler, dir string) http.Handler {
fs := http.FileServer(http.Dir(dir))
indexPath := filepath.Join(dir, "index.html")
signalPaths := map[string]bool{"/agent/signal": true, "/attach": true, "/pair": true, "/turn-credentials": true, "/healthz": true}
// Every path the signal server owns must be forwarded here, or --webroot mode
// 404s it into the static file server. This list MUST stay in sync with the
// routes registered in signal.Server.Handler(); main_test.go's
// TestWithStaticForwardsSignalingPaths guards against drift (it caught /registry
// going missing in production).
signalPaths := map[string]bool{"/agent/signal": true, "/attach": true, "/pair": true, "/turn-credentials": true, "/healthz": true, "/registry": true}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if signalPaths[r.URL.Path] {
sig.ServeHTTP(w, r)
Expand Down
40 changes: 40 additions & 0 deletions go/cmd/mir-signal/main_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,55 @@
package main

import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/srcful/terminal-relay/go/internal/signal"
)

// TestWithStaticForwardsSignalingPaths guards the production --webroot wiring: every
// path the signal server owns must be forwarded to it, not 404'd into the static
// file server. /registry shipped registered in signal.Server.Handler() but MISSING
// from withStatic's signalPaths map, so it 404'd in production (tests that used
// Server.Handler() directly never saw the wrapper). This pins the wrapper itself.
func TestWithStaticForwardsSignalingPaths(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte("<html>spa</html>"), 0o644); err != nil {
t.Fatal(err)
}
ts := httptest.NewServer(withStatic(signal.New().Handler(), dir))
defer ts.Close()

// /registry MUST be forwarded to the signal server (handleRegistry returns a JSON
// array for a wallet query) — not 404'd into the static FS.
resp, err := http.Get(ts.URL + "/registry?wallet=test")
if err != nil {
t.Fatal(err)
}
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != http.StatusOK || !strings.HasPrefix(strings.TrimSpace(string(body)), "[") {
t.Fatalf("/registry via withStatic = %d %q; want 200 JSON array (forwarded to signal, not static 404)", resp.StatusCode, body)
}

// /healthz is forwarded too.
if r, err := http.Get(ts.URL + "/healthz"); err != nil || r.StatusCode != http.StatusOK {
t.Fatalf("/healthz via withStatic not forwarded (err=%v)", err)
}

// A missing static asset still 404s via the FS — proves we aren't trivially
// forwarding everything to the signal server.
if r, err := http.Get(ts.URL + "/vendor/missing-asset.js"); err != nil || r.StatusCode != http.StatusNotFound {
t.Fatalf("a missing static asset should 404 via the FS (got err=%v)", err)
}
}

func TestNewHTTPServerSetsTimeouts(t *testing.T) {
handler := http.NewServeMux()
srv := newHTTPServer(":0", handler)
Expand Down
2 changes: 1 addition & 1 deletion web/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const SHELL = [
// Live conversations with the relay — never cached, never intercepted.
// (WebSocket upgrades bypass the fetch handler anyway; this covers the plain
// HTTP ones like /turn-credentials and keeps the list in one place.)
const SIGNALING = ['/agent/signal', '/attach', '/pair', '/turn-credentials', '/healthz'];
const SIGNALING = ['/agent/signal', '/attach', '/pair', '/turn-credentials', '/healthz', '/registry'];

self.addEventListener('install', (event) => {
event.waitUntil(
Expand Down
Loading