package apps import ( "crypto/sha256" "encoding/hex" _ "embed" "sync" ) // Embedded fallback: tool HTMLs from the time the binary was built. // Used as a last-resort served-bytes when (cache miss) AND (upstream // unreachable) AND (no operator override) — see handler.go. // // The files are populated by the top-level build.sh, which copies the // freshly-built dist/.html into ./embedded/ before `go build` runs. // Empty placeholder files are checked in so the package compiles when no // build has been run yet (CI bootstrap, fresh clone, etc.); at runtime an // empty embedded body is treated as "no embedded fallback available." //go:embed embedded/archive.html var embeddedArchive []byte //go:embed embedded/transmittal.html var embeddedTransmittal []byte //go:embed embedded/classifier.html var embeddedClassifier []byte //go:embed embedded/index.html var embeddedLanding []byte //go:embed embedded/browse.html var embeddedBrowse []byte // EmbeddedBytes returns the embedded HTML for app, or nil if either app is // not one of the canonical names or the embedded slot is empty (no build // has populated it). func EmbeddedBytes(app string) []byte { var b []byte switch app { case "archive": b = embeddedArchive case "transmittal": b = embeddedTransmittal case "classifier": b = embeddedClassifier case "landing": b = embeddedLanding case "browse": b = embeddedBrowse default: return nil } if len(b) == 0 { return nil } return b } // EmbeddedETag returns a strong ETag (sha256-hex prefix, 32 chars) for the // app's embedded bytes. Computed lazily on first call per-app and memoized // — the embedded slot is fixed for the binary's lifetime, so the ETag // changes only when the binary is redeployed. Empty slot returns "". // // Used by apps.Server.serveEmbedded to issue conditional-GET-friendly // responses: with this ETag + Cache-Control: max-age=0, must-revalidate, // every page load revalidates and gets a 304 unless the binary has been // updated. Saves re-transmitting 50–920 KB tool HTMLs on every reload. func EmbeddedETag(app string) string { if v, ok := etagCacheByApp.Load(app); ok { return v.(string) } body := EmbeddedBytes(app) if body == nil { return "" } sum := sha256.Sum256(body) etag := hex.EncodeToString(sum[:])[:32] etagCacheByApp.Store(app, etag) return etag } // etagCacheByApp memoizes EmbeddedETag results keyed by app name. var etagCacheByApp sync.Map