Нет соединения
++ glchat не смог связаться с сервером. Проверьте сеть — как только связь вернётся, приложение + откроется само. +
+ +diff --git a/internal/server/cosmetics_test.go b/internal/server/cosmetics_test.go index 912f5a4..d66bcf6 100644 --- a/internal/server/cosmetics_test.go +++ b/internal/server/cosmetics_test.go @@ -785,3 +785,22 @@ func guildChannelID(t *testing.T, srv *Server, guildID string, cookie *http.Cook t.Fatal("текстовая комната не найдена") return "" } + +func TestStaticServesPWAAssets(t *testing.T) { + srv, _ := newTestServer(t) + // В тестовом каталоге лежит только index.html: проверяем типы по расширению. + cases := map[string]string{ + "/manifest.webmanifest": "application/manifest+json", + "/sw.js": "text/javascript; charset=utf-8", + "/offline.html": "text/html; charset=utf-8", + } + for path, want := range cases { + rec := doJSON(t, srv, http.MethodGet, path, "") + if rec.Code != http.StatusOK { + t.Fatalf("%s = %d", path, rec.Code) + } + if got := rec.Header().Get("Content-Type"); got != want { + t.Fatalf("%s: Content-Type = %q, ожидался %q", path, got, want) + } + } +} diff --git a/internal/server/static.go b/internal/server/static.go index 517d308..29f6d38 100644 --- a/internal/server/static.go +++ b/internal/server/static.go @@ -39,7 +39,9 @@ func (h *staticHandler) serve(w http.ResponseWriter, r *http.Request) { return } - if contentType := mime.TypeByExtension(filepath.Ext(rel)); contentType != "" { + // Часть расширений (манифест PWA) известна не всем системам: тип задаём + // явно, иначе браузер получает text/plain и отказывается ставить приложение. + if contentType := staticContentType(rel); contentType != "" { w.Header().Set("Content-Type", contentType) } if strings.HasPrefix(rel, "assets/") { @@ -50,6 +52,21 @@ func (h *staticHandler) serve(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, filepath.Join(h.root, filepath.FromSlash(rel))) } +// staticContentType определяет тип файла: сначала явные правила, потом mime. +func staticContentType(rel string) string { + switch filepath.Ext(rel) { + case ".webmanifest": + return "application/manifest+json" + case ".js": + return "text/javascript; charset=utf-8" + case ".css": + return "text/css; charset=utf-8" + case ".svg": + return "image/svg+xml" + } + return mime.TypeByExtension(filepath.Ext(rel)) +} + func (h *staticHandler) serveIndex(w http.ResponseWriter, r *http.Request) { index := filepath.Join(h.root, "index.html") if _, err := os.Stat(index); err != nil { diff --git a/internal/server/testdata/web/icons/icon-192.png b/internal/server/testdata/web/icons/icon-192.png new file mode 100644 index 0000000..cecdad6 Binary files /dev/null and b/internal/server/testdata/web/icons/icon-192.png differ diff --git a/internal/server/testdata/web/manifest.webmanifest b/internal/server/testdata/web/manifest.webmanifest new file mode 100644 index 0000000..cf036f1 --- /dev/null +++ b/internal/server/testdata/web/manifest.webmanifest @@ -0,0 +1,29 @@ +{ + "name": "glchat", + "short_name": "glchat", + "description": "glchat — self-hosted платформа общения: серверы, комнаты, голос и файлы", + "lang": "ru", + "dir": "ltr", + "start_url": "/app", + "scope": "/", + "display": "standalone", + "display_override": ["window-controls-overlay", "standalone"], + "orientation": "any", + "background_color": "#1a1b1e", + "theme_color": "#1a1b1e", + "categories": ["social", "communication"], + "icons": [ + { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }, + { + "src": "/icons/maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "shortcuts": [ + { "name": "Друзья", "url": "/app/friends" }, + { "name": "Настройки", "url": "/settings/account/profile" } + ] +} diff --git a/internal/server/testdata/web/offline.html b/internal/server/testdata/web/offline.html new file mode 100644 index 0000000..75ab26a --- /dev/null +++ b/internal/server/testdata/web/offline.html @@ -0,0 +1,60 @@ + + +
+ + + ++ glchat не смог связаться с сервером. Проверьте сеть — как только связь вернётся, приложение + откроется само. +
+ +