diff --git a/server/server.go b/server/server.go index aeeb423d..b6e07b3b 100644 --- a/server/server.go +++ b/server/server.go @@ -679,22 +679,6 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit return errHTTPNotFound } -// handleWebApp serves the embedded web app's index for client-side (SPA) routes that the -// browser router resolves, so the app shell loads and the client-side router takes over. -func (s *Server) handleWebApp(w http.ResponseWriter, r *http.Request, v *visitor) error { - r.URL.Path = webAppIndex - return s.handleStatic(w, r, v) -} - -// handleWebAppNoIndex serves the web app index for the magic-link landing pages, whose path -// carries a one-time token. The response is marked no-referrer (so the token can't leak to third -// parties via the Referer header) and noindex (so it never gets indexed). -func (s *Server) handleWebAppNoIndex(w http.ResponseWriter, r *http.Request, v *visitor) error { - w.Header().Set("Referrer-Policy", "no-referrer") - w.Header().Set("X-Robots-Tag", "noindex") - return s.handleWebApp(w, r, v) -} - func (s *Server) handleTopic(w http.ResponseWriter, r *http.Request, v *visitor) error { unifiedpush := readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see PUT/POST too! if unifiedpush { @@ -721,61 +705,6 @@ func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request, _ *visitor return s.writeJSON(w, response) } -func (s *Server) handleConfig(w http.ResponseWriter, _ *http.Request, _ *visitor) error { - w.Header().Set("Cache-Control", "no-cache") - return s.writeJSON(w, s.configResponse()) -} - -func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visitor) error { - b, err := json.MarshalIndent(s.configResponse(), "", " ") - if err != nil { - return err - } - w.Header().Set("Content-Type", "text/javascript") - w.Header().Set("Cache-Control", "no-cache") - _, err = io.WriteString(w, fmt.Sprintf("// Generated server configuration\nvar config = %s;\n", string(b))) - return err -} - -func (s *Server) configResponse() *apiConfigResponse { - return &apiConfigResponse{ - BaseURL: "", // Will translate to window.location.origin - AppRoot: s.config.WebRoot, - EnableLogin: s.config.EnableLogin, - RequireLogin: s.config.RequireLogin, - EnableSignup: s.config.EnableSignup, - EnablePayments: s.config.StripeSecretKey != "", - EnableCalls: s.config.TwilioAccount != "", - EnableEmails: s.config.SMTPSenderFrom != "", - EnableResetPassword: s.config.SMTPSenderFrom != "" && s.config.BaseURL != "", // Reset links need SMTP + an absolute base-url - EnableReservations: s.config.EnableReservations, - EnableWebPush: s.config.WebPushPublicKey != "", - BillingContact: s.config.BillingContact, - WebPushPublicKey: s.config.WebPushPublicKey, - DisallowedTopics: s.config.DisallowedTopics, - ConfigHash: s.config.Hash(), - } -} - -// handleWebManifest serves the web app manifest for the progressive web app (PWA) -func (s *Server) handleWebManifest(w http.ResponseWriter, _ *http.Request, _ *visitor) error { - response := &webManifestResponse{ - Name: "ntfy", - Description: "ntfy lets you send push notifications via scripts from any computer or phone", - ShortName: "ntfy", - Scope: "/", - StartURL: s.config.WebRoot, - Display: "standalone", - BackgroundColor: "#ffffff", - ThemeColor: "#317f6f", - Icons: []*webManifestIcon{ - {SRC: "/static/images/pwa-192x192.png", Sizes: "192x192", Type: "image/png"}, - {SRC: "/static/images/pwa-512x512.png", Sizes: "512x512", Type: "image/png"}, - }, - } - return s.writeJSONWithContentType(w, response, "application/manifest+json") -} - // handleMetrics returns Prometheus metrics. This endpoint is only called if enable-metrics is set, // and listen-metrics-http is not set. func (s *Server) handleMetrics(w http.ResponseWriter, r *http.Request, _ *visitor) error { @@ -783,19 +712,6 @@ func (s *Server) handleMetrics(w http.ResponseWriter, r *http.Request, _ *visito return nil } -// handleStatic returns all static resources (excluding the docs), including the web app -func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request, _ *visitor) error { - r.URL.Path = webSiteDir + r.URL.Path - util.Gzip(http.FileServer(http.FS(webFsCached))).ServeHTTP(w, r) - return nil -} - -// handleDocs returns static resources related to the docs -func (s *Server) handleDocs(w http.ResponseWriter, r *http.Request, _ *visitor) error { - util.Gzip(http.FileServer(http.FS(docsStaticCached))).ServeHTTP(w, r) - return nil -} - // handleStats returns the publicly available server stats func (s *Server) handleStats(w http.ResponseWriter, _ *http.Request, _ *visitor) error { s.mu.RLock() diff --git a/server/server_web.go b/server/server_web.go new file mode 100644 index 00000000..5c350dd1 --- /dev/null +++ b/server/server_web.go @@ -0,0 +1,94 @@ +package server + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "heckel.io/ntfy/v2/util" +) + +// handleWebApp serves the embedded web app's index for client-side (SPA) routes that the +// browser router resolves, so the app shell loads and the client-side router takes over. +func (s *Server) handleWebApp(w http.ResponseWriter, r *http.Request, v *visitor) error { + r.URL.Path = webAppIndex + return s.handleStatic(w, r, v) +} + +// handleWebAppNoIndex serves the web app index for the magic-link landing pages, whose path +// carries a one-time token. The response is marked no-referrer (so the token can't leak to third +// parties via the Referer header) and noindex (so it never gets indexed). +func (s *Server) handleWebAppNoIndex(w http.ResponseWriter, r *http.Request, v *visitor) error { + w.Header().Set("Referrer-Policy", "no-referrer") + w.Header().Set("X-Robots-Tag", "noindex") + return s.handleWebApp(w, r, v) +} + +func (s *Server) handleConfig(w http.ResponseWriter, _ *http.Request, _ *visitor) error { + w.Header().Set("Cache-Control", "no-cache") + return s.writeJSON(w, s.configResponse()) +} + +func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visitor) error { + b, err := json.MarshalIndent(s.configResponse(), "", " ") + if err != nil { + return err + } + w.Header().Set("Content-Type", "text/javascript") + w.Header().Set("Cache-Control", "no-cache") + _, err = io.WriteString(w, fmt.Sprintf("// Generated server configuration\nvar config = %s;\n", string(b))) + return err +} + +// handleWebManifest serves the web app manifest for the progressive web app (PWA) +func (s *Server) handleWebManifest(w http.ResponseWriter, _ *http.Request, _ *visitor) error { + response := &webManifestResponse{ + Name: "ntfy", + Description: "ntfy lets you send push notifications via scripts from any computer or phone", + ShortName: "ntfy", + Scope: "/", + StartURL: s.config.WebRoot, + Display: "standalone", + BackgroundColor: "#ffffff", + ThemeColor: "#317f6f", + Icons: []*webManifestIcon{ + {SRC: "/static/images/pwa-192x192.png", Sizes: "192x192", Type: "image/png"}, + {SRC: "/static/images/pwa-512x512.png", Sizes: "512x512", Type: "image/png"}, + }, + } + return s.writeJSONWithContentType(w, response, "application/manifest+json") +} + +// handleStatic returns all static resources (excluding the docs), including the web app +func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request, _ *visitor) error { + r.URL.Path = webSiteDir + r.URL.Path + util.Gzip(http.FileServer(http.FS(webFsCached))).ServeHTTP(w, r) + return nil +} + +// handleDocs returns static resources related to the docs +func (s *Server) handleDocs(w http.ResponseWriter, r *http.Request, _ *visitor) error { + util.Gzip(http.FileServer(http.FS(docsStaticCached))).ServeHTTP(w, r) + return nil +} + +func (s *Server) configResponse() *apiConfigResponse { + return &apiConfigResponse{ + BaseURL: "", // Will translate to window.location.origin + AppRoot: s.config.WebRoot, + EnableLogin: s.config.EnableLogin, + RequireLogin: s.config.RequireLogin, + EnableSignup: s.config.EnableSignup, + EnablePayments: s.config.StripeSecretKey != "", + EnableCalls: s.config.TwilioAccount != "", + EnableEmails: s.config.SMTPSenderFrom != "", + EnableResetPassword: s.config.SMTPSenderFrom != "" && s.config.BaseURL != "", // Reset links need SMTP + an absolute base-url + EnableReservations: s.config.EnableReservations, + EnableWebPush: s.config.WebPushPublicKey != "", + BillingContact: s.config.BillingContact, + WebPushPublicKey: s.config.WebPushPublicKey, + DisallowedTopics: s.config.DisallowedTopics, + ConfigHash: s.config.Hash(), + } +}