Staff push notifications, and revalidate embedded assets
Notifications: staff turn them on per device from a new Notifications page, pick each type (bans, gags, mutes and silences added or lifted, server down and back up) and quiet hours. A watcher polls SimpleAdmin's tables, so in-game penalties are sent too. Web Push with VAPID and RFC 8291 encryption in internal/push, standard library only. Tables in sql/push.sql, created at startup; the VAPID key is made on first start and kept in saw_settings. Penalty notifications link to the public list searching for the player (#bans?q=<steamid64>). The Android app delegates notifications (versionCode 3). Caching: pages, scripts and styles are served with Cache-Control: no-cache and an ETag, so a deploy shows up at once instead of after Cloudflare's default 4-hour browser cache.
This commit is contained in:
parent
7900bf5b3e
commit
17232bd5a2
23 changed files with 2135 additions and 12 deletions
47
internal/web/assets.go
Normal file
47
internal/web/assets.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// The pages, scripts and styles are embedded, so they only change with a new build. They're served
|
||||
// with Cache-Control: no-cache and an ETag (a hash of the file): browsers, the Android app and
|
||||
// Cloudflare check each time and get a quick 304 while the file is unchanged, and a new deploy
|
||||
// shows up at once. Without these headers Cloudflare tells browsers to keep them for 4 hours.
|
||||
|
||||
var assetTags = sync.OnceValue(func() map[string]string {
|
||||
tags := map[string]string{}
|
||||
static, _ := fs.Sub(assets, "assets")
|
||||
_ = fs.WalkDir(static, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return err
|
||||
}
|
||||
data, err := fs.ReadFile(static, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sum := sha256.Sum256(data)
|
||||
tags[path] = `"` + hex.EncodeToString(sum[:8]) + `"`
|
||||
return nil
|
||||
})
|
||||
return tags
|
||||
})
|
||||
|
||||
// revalidate sets the caching headers for an embedded file. http.ServeContent answers a matching
|
||||
// If-None-Match with 304 from the ETag set here.
|
||||
func revalidate(w http.ResponseWriter, name string) {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
if tag, ok := assetTags()[name]; ok {
|
||||
w.Header().Set("ETag", tag)
|
||||
}
|
||||
}
|
||||
|
||||
// serveAsset serves one embedded file, see revalidate.
|
||||
func serveAsset(w http.ResponseWriter, r *http.Request, static fs.FS, name string) {
|
||||
revalidate(w, name)
|
||||
http.ServeFileFS(w, r, static, name)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue