diff --git a/backend/internal/web/embed_on.go b/backend/internal/web/embed_on.go index f7ba5c9e5..ca1b6f7ea 100644 --- a/backend/internal/web/embed_on.go +++ b/backend/internal/web/embed_on.go @@ -10,6 +10,7 @@ import ( "io" "io/fs" "net/http" + "reflect" "strings" "time" @@ -17,6 +18,30 @@ import ( "github.com/gin-gonic/gin" ) +// extractSiteLogo extracts the site_logo field from the settings struct using reflection +// This avoids tight coupling with the specific struct type returned by GetPublicSettingsForInjection +func extractSiteLogo(settings any) string { + if settings == nil { + return "" + } + + // Try to access SiteLogo field directly via reflection + v := reflect.ValueOf(settings) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() != reflect.Struct { + return "" + } + + field := v.FieldByName("SiteLogo") + if field.IsValid() && field.Kind() == reflect.String { + return field.String() + } + + return "" +} + const ( // NonceHTMLPlaceholder is the placeholder for nonce in HTML script tags NonceHTMLPlaceholder = "__CSP_NONCE_VALUE__" @@ -165,7 +190,10 @@ func (s *FrontendServer) serveIndexHTML(c *gin.Context) { return } - rendered := s.injectSettings(settingsJSON) + // Extract site_logo for favicon replacement + siteLogo := extractSiteLogo(settings) + + rendered := s.injectSettings(settingsJSON, siteLogo) s.cache.Set(rendered, settingsJSON) // Replace nonce placeholder with actual nonce before serving @@ -180,14 +208,26 @@ func (s *FrontendServer) serveIndexHTML(c *gin.Context) { c.Abort() } -func (s *FrontendServer) injectSettings(settingsJSON []byte) []byte { +func (s *FrontendServer) injectSettings(settingsJSON []byte, siteLogo string) []byte { + html := s.baseHTML + + // If custom site logo is set, replace the default favicon in HTML + // This prevents the default logo from flashing before JS loads + if siteLogo != "" { + // Replace with custom logo + // Match various forms of the favicon link tag + faviconPattern := []byte(``) + newFavicon := []byte(``) + html = bytes.Replace(html, faviconPattern, newFavicon, 1) + } + // Create the script tag to inject with nonce placeholder // The placeholder will be replaced with actual nonce at request time script := []byte(``) // Inject before headClose := []byte("") - return bytes.Replace(s.baseHTML, headClose, append(script, headClose...), 1) + return bytes.Replace(html, headClose, append(script, headClose...), 1) } // replaceNoncePlaceholder replaces the nonce placeholder with actual nonce value diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d88c6eed1..977dcc6a8 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -20,8 +20,20 @@ function injectPublicSettings(backendUrl: string): Plugin { if (response.ok) { const data = await response.json() if (data.code === 0 && data.data) { + // Inject config script const script = `` - return html.replace('', `${script}\n`) + let result = html.replace('', `${script}\n`) + + // Replace favicon with custom logo if set (prevents default logo flash) + const siteLogo = data.data.site_logo + if (siteLogo) { + result = result.replace( + '', + `` + ) + } + + return result } } } catch (e) {