web.go raw

   1  package app
   2  
   3  import (
   4  	"embed"
   5  	"io/fs"
   6  	"net/http"
   7  	"strings"
   8  )
   9  
  10  //go:embed smesh/dist
  11  var reactAppFS embed.FS
  12  
  13  var webDistFS fs.FS
  14  
  15  func getWebDistFS() fs.FS {
  16  	if webDistFS == nil {
  17  		var err error
  18  		webDistFS, err = fs.Sub(reactAppFS, "smesh/dist")
  19  		if err != nil {
  20  			panic("Failed to load embedded web app: " + err.Error())
  21  		}
  22  	}
  23  	return webDistFS
  24  }
  25  
  26  // GetReactAppFS returns a http.FileSystem from the embedded React app
  27  func GetReactAppFS() http.FileSystem {
  28  	return http.FS(getWebDistFS())
  29  }
  30  
  31  // ServeEmbeddedWeb serves the embedded web application with SPA fallback routing.
  32  // For paths that don't map to a real file, serve index.html so React router can handle them.
  33  func ServeEmbeddedWeb(w http.ResponseWriter, r *http.Request) {
  34  	distFS := getWebDistFS()
  35  	fileServer := http.FileServer(http.FS(distFS))
  36  
  37  	path := r.URL.Path
  38  	if path == "/" {
  39  		fileServer.ServeHTTP(w, r)
  40  		return
  41  	}
  42  
  43  	// Check if the path maps to a real file in the embedded FS
  44  	cleanPath := strings.TrimPrefix(path, "/")
  45  	if f, err := distFS.Open(cleanPath); err == nil {
  46  		f.Close()
  47  		fileServer.ServeHTTP(w, r)
  48  		return
  49  	}
  50  
  51  	// SPA fallback: serve index.html for client-side routing
  52  	r.URL.Path = "/"
  53  	fileServer.ServeHTTP(w, r)
  54  }
  55  
  56  // GetEmbeddedWebFS returns the raw embedded filesystem for branding initialization.
  57  // This is used by the init-branding command to extract default assets.
  58  func GetEmbeddedWebFS() embed.FS {
  59  	return reactAppFS
  60  }
  61