package web import "net/http" // flashCookie carries a one-shot notification code between a POST→redirect and // the next rendered page (PRG pattern). The value is a non-sensitive code (not // user data), so it is not signed; HttpOnly + SameSite=Lax + a short MaxAge bound // it. The flashBanner component maps the code to a styled message. const flashCookie = "tapir_flash" // Flash codes. Kept small and stable — the message + severity live in // flashMessages (view.go), not here, so the cookie never carries free text. const ( flashConnected = "connected" flashConnectFailed = "connect_failed" flashDisconnected = "disconnected" flashDeleted = "deleted" flashRegistered = "registered" ) // flashMaxAge bounds how long an unread flash lingers (seconds). Long enough to // survive the redirect, short enough that a stale banner never reappears. const flashMaxAge = 60 // setFlash queues a one-shot notification surfaced by the next full page render. func setFlash(w http.ResponseWriter, code string) { http.SetCookie(w, &http.Cookie{ Name: flashCookie, Value: code, Path: "/", MaxAge: flashMaxAge, HttpOnly: true, SameSite: http.SameSiteLaxMode, }) } // takeFlash returns the pending flash code (if any) and clears the cookie so the // banner shows exactly once. Call it only on full-page renders, not HTMX // fragments, so a fragment swap never consumes a flash meant for the next page. func takeFlash(w http.ResponseWriter, r *http.Request) string { c, err := r.Cookie(flashCookie) if err != nil || c.Value == "" { return "" } http.SetCookie(w, &http.Cookie{ Name: flashCookie, Value: "", Path: "/", MaxAge: -1, HttpOnly: true, SameSite: http.SameSiteLaxMode, }) return c.Value }