From 3b19091547e7a13ef30b52094f68d1060f60aa81 Mon Sep 17 00:00:00 2001 From: n0ctal <4c866w5fn9@privaterelay.appleid.com> Date: Fri, 14 Aug 2026 23:00:51 +0500 Subject: [PATCH] fix(sub): render the full remark once per subscription, not once per credential (#6198) * fix(sub): scope full remarks to subscription identity * fix(sub): preserve configured remark whitespace --------- Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com> --- internal/sub/remark_vars.go | 16 ++++++++++++---- internal/sub/remark_vars_test.go | 27 +++++++++++++++++++++++++-- internal/sub/service.go | 5 ++--- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/internal/sub/remark_vars.go b/internal/sub/remark_vars.go index 6f73ce81c..2052ad71b 100644 --- a/internal/sub/remark_vars.go +++ b/internal/sub/remark_vars.go @@ -607,19 +607,27 @@ func appendKeptRun(runs []string, run string, leftRemoved, rightRemoved bool) [] return runs } -func (s *SubService) effectiveTemplate(email string) string { +func templateInfoKey(client model.Client) string { + if client.SubID != "" { + return "sub:" + client.SubID + } + return "email:" + client.Email +} + +func (s *SubService) effectiveTemplate(client model.Client) string { translated := translateUISingleBrackets(s.remarkTemplate) if s.usageShown == nil { s.usageShown = map[string]bool{} } - if s.usageShown[email] { + key := templateInfoKey(client) + if s.usageShown[key] { remove := firstLinkOnlyBodyTokens if s.showIdentityOnAllLinks { remove = usageInfoTokens } return filterRemarkTemplate(translated, remove) } - s.usageShown[email] = true + s.usageShown[key] = true return translated } @@ -646,7 +654,7 @@ func (s *SubService) genTemplatedRemark(inbound *model.Inbound, client model.Cli } var tmpl string if s.subscriptionBody { - tmpl = s.effectiveTemplate(client.Email) + tmpl = s.effectiveTemplate(client) } else { tmpl = filterRemarkTemplate(translateUISingleBrackets(s.remarkTemplate), displayRemoveTokens) } diff --git a/internal/sub/remark_vars_test.go b/internal/sub/remark_vars_test.go index 3907051d6..23d396c68 100644 --- a/internal/sub/remark_vars_test.go +++ b/internal/sub/remark_vars_test.go @@ -649,7 +649,6 @@ func TestUsageOnFirstLinkOnly_SingleBracket(t *testing.T) { } client := model.Client{Email: "alice@x"} first := s.genTemplatedRemark(inbound, client, "", "ws") - s.usageShown["alice@x"] = true second := s.genTemplatedRemark(inbound, client, "", "ws") if !strings.Contains(first, "📊") { t.Fatalf("first link should carry usage: %q", first) @@ -675,7 +674,6 @@ func TestEmailOnFirstLinkOnly(t *testing.T) { } client := model.Client{Email: "alice@x"} first := s.genTemplatedRemark(inbound, client, "", "ws") - s.usageShown["alice@x"] = true second := s.genTemplatedRemark(inbound, client, "", "ws") if !strings.Contains(first, "alice@x") { t.Fatalf("first link should carry email: %q", first) @@ -724,3 +722,28 @@ func TestIdentityOnAllLinks(t *testing.T) { }) } } + +func TestSharedSubIDRemark_FullInfoOncePerSubscription(t *testing.T) { + const tmpl = "{{INBOUND}}-{{EMAIL}}" + s := &SubService{ + remarkTemplate: tmpl, + subscriptionBody: true, + usageShown: map[string]bool{}, + } + first := model.Client{Email: "first@example", SubID: "shared-sub"} + second := model.Client{Email: "second@example", SubID: "shared-sub"} + if got := s.genTemplatedRemark(&model.Inbound{Remark: "DE"}, first, "", "tcp"); got != "DE-first@example" { + t.Fatalf("first credential remark = %q", got) + } + if got := s.genTemplatedRemark(&model.Inbound{Remark: "FI"}, second, "", "tcp"); got != "FI" { + t.Fatalf("second credential with shared subId remark = %q, want identity suppressed", got) + } +} + +func TestGenTemplatedRemarkPreservesConfiguredOuterWhitespace(t *testing.T) { + s := &SubService{remarkTemplate: " {{INBOUND}} ", subscriptionBody: true, usageShown: map[string]bool{}} + got := s.genTemplatedRemark(&model.Inbound{Remark: "DE"}, model.Client{Email: "user@example.test"}, "", "tcp") + if got != " DE " { + t.Fatalf("remark = %q, want configured outer whitespace preserved", got) + } +} diff --git a/internal/sub/service.go b/internal/sub/service.go index 0ebfa1c02..b6d2f5ba3 100644 --- a/internal/sub/service.go +++ b/internal/sub/service.go @@ -37,9 +37,8 @@ type SubService struct { // other context — the sub info page, the panel's link/QR displays — renders // the name-only template, like Remnawave. subscriptionBody bool - // usageShown tracks, per client email, whether the info part of the template - // has already been emitted this request, so it appears on the first body - // link only. Per-request state; reset in PrepareForRequest. + // usageShown emits info once per subscription identity, including twins. + // PrepareForRequest resets this per-request state. usageShown map[string]bool showIdentityOnAllLinks bool inboundService service.InboundService