Add installable PWA shell with safe updates and offline status
This commit is contained in:
1 parent
64915a7b94
commit
1562519f08
18 files changed
+310
-5
No files matched your search
+4
-1
@@ -124,6 +124,9 @@ func (s *Server) Handler(static fs.FS) http.Handler {
|
||||
func securityHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := w.Header()
|
||||
if strings.HasPrefix(r.URL.Path, "/api/") {
|
||||
h.Set("Cache-Control", "no-store")
|
||||
}
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
h.Set("Referrer-Policy", "same-origin")
|
||||
h.Set("X-Frame-Options", "DENY")
|
||||
@@ -394,7 +397,7 @@ func (s *Server) serveRaw(w http.ResponseWriter, r *http.Request, rel string) {
|
||||
if ct := mime.TypeByExtension(path.Ext(info.Name())); ct != "" {
|
||||
h.Set("Content-Type", ct)
|
||||
}
|
||||
h.Set("Cache-Control", "private, no-cache")
|
||||
h.Set("Cache-Control", "no-store")
|
||||
http.ServeContent(w, r, info.Name(), info.ModTime(), f)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -139,6 +140,41 @@ func TestOpenAPICoversRoutes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIResponsesAreNotCached(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.write("笔记.md", "# 正文")
|
||||
// 登录状态与目录曾在 Firefox 离线时命中 HTTP 缓存,误进入无法加载的工作区。
|
||||
for _, path := range []string{"/api/v1/auth/state", "/api/v1/tree", "/api/v1/files/笔记.md", "/api/v1/raw/笔记.md"} {
|
||||
res := f.do(f.admin, "GET", path, "", nil)
|
||||
if res.StatusCode != http.StatusOK || res.Header.Get("Cache-Control") != "no-store" {
|
||||
t.Errorf("%s:状态 %d,缓存策略 %q", path, res.StatusCode, res.Header.Get("Cache-Control"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompressedManifestContentType(t *testing.T) {
|
||||
body := strings.Repeat(" ", 300) + `{"name":"RawMark","display":"standalone"}`
|
||||
handler := newStaticHandler(fstest.MapFS{"manifest.webmanifest": {Data: []byte(body)}})
|
||||
req := httptest.NewRequest("GET", "/manifest.webmanifest", nil)
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.ServeHTTP(recorder, req)
|
||||
res := recorder.Result()
|
||||
defer res.Body.Close()
|
||||
if res.Header.Get("Content-Type") != "application/manifest+json" || res.Header.Get("Content-Encoding") != "gzip" {
|
||||
t.Fatalf("清单响应头不正确:%v", res.Header)
|
||||
}
|
||||
zr, err := gzip.NewReader(res.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer zr.Close()
|
||||
var manifest map[string]string
|
||||
if err := json.NewDecoder(zr).Decode(&manifest); err != nil || manifest["name"] != "RawMark" {
|
||||
t.Fatalf("压缩清单无法解析:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShareAccess(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
f.write("目录/笔记.md", "# 标题\n\n\n")
|
||||
|
||||
@@ -68,7 +68,10 @@ func (h *staticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if share {
|
||||
hd.Set("X-Robots-Tag", "noindex")
|
||||
}
|
||||
if ct := mime.TypeByExtension(path.Ext(name)); ct != "" {
|
||||
if path.Ext(name) == ".webmanifest" {
|
||||
// Go 的 MIME 表未必包含此扩展名,不能让 ServeContent 对压缩后的数据嗅探类型。
|
||||
hd.Set("Content-Type", "application/manifest+json")
|
||||
} else if ct := mime.TypeByExtension(path.Ext(name)); ct != "" {
|
||||
hd.Set("Content-Type", ct)
|
||||
}
|
||||
hd.Add("Vary", "Accept-Encoding")
|
||||
|
||||
Reference in new issue
Block a user