// Package spa provides the HTTP handler that serves a single-page application. package spa import ( "net/http" "path/filepath" "code.nochebuena.dev/einherjar/contracts/logging" ) type handler struct { logger logging.Logger staticDir string fs http.Handler } // NewHandler returns an http.Handler that serves static files from staticDir. // Requests for paths that exist on disk are served directly. // Any other path receives index.html, delegating routing to the SPA. func NewHandler(logger logging.Logger, staticDir string) http.Handler { return &handler{ logger: logger, staticDir: staticDir, fs: http.FileServer(http.Dir(staticDir)), } } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // http.Dir.Open sanitises the path, preventing directory traversal. f, err := http.Dir(h.staticDir).Open(r.URL.Path) if err != nil { // Path does not exist — hand off to the SPA router. http.ServeFile(w, r, filepath.Join(h.staticDir, "index.html")) return } defer f.Close() stat, err := f.Stat() if err != nil || stat.IsDir() { // Directory listing is disabled; treat directories as SPA routes. http.ServeFile(w, r, filepath.Join(h.staticDir, "index.html")) return } h.fs.ServeHTTP(w, r) }