diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index e0f89656fd..55c625c174 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -875,6 +875,7 @@ func Routes() *web.Router { })) } + m.AfterRouting(common.SessionTagMiddleware()) m.AfterRouting(context.APIContexter()) m.AfterRouting(checkDeprecatedAuthMethods) diff --git a/routers/common/session_tag.go b/routers/common/session_tag.go new file mode 100644 index 0000000000..bd91fc2421 --- /dev/null +++ b/routers/common/session_tag.go @@ -0,0 +1,34 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + "net/http" + + "code.gitea.io/gitea/services/project_events" +) + +// SessionTagHeader is the HTTP header browser tabs use to broadcast a +// per-page-load identifier with every mutation request. The server +// echoes it back inside SSE event payloads so the originating tab +// can suppress its own event after applying the optimistic update. +const SessionTagHeader = "X-Session-Tag" + +// SessionTagMiddleware decorates each incoming request's context with +// the X-Session-Tag header value when present. Service- and model- +// layer publishers read the value via project_events.SessionTagFromContext. +// +// Empty / missing headers are a no-op. +func SessionTagMiddleware() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tag := r.Header.Get(SessionTagHeader) + if tag != "" { + ctx := project_events.WithSessionTag(r.Context(), tag) + r = r.WithContext(ctx) + } + next.ServeHTTP(w, r) + }) + } +} diff --git a/routers/web/web.go b/routers/web/web.go index ecd75250d2..62927df442 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -295,7 +295,7 @@ func Routes() *web.Router { routes.Get("/ssh_info", misc.SSHInfo) routes.Get("/api/healthz", healthcheck.Check) - mid = append(mid, common.MustInitSessioner(), context.Contexter()) + mid = append(mid, common.SessionTagMiddleware(), common.MustInitSessioner(), context.Contexter()) // Get user from session if logged in. webAuth := newWebAuthMiddleware()