Two issues raised by the Copilot review:
1) subscriptionExpiryFromClient called time.Now() per invocation.
Two clients with the same delayed-start duration normalized to
timestamps a few milliseconds apart, so the aggregator's
"if normalized != traffic.ExpiryTime" check tripped and the
subscription header expire= dropped back to 0 — the exact bug
the helper was meant to fix, just one client later.
Take nowMs as a parameter; each of GetSubs / GetClash / GetConfig
captures one timestamp per request and reuses it.
2) Guarding Flow against empty incoming values in SyncInbound
prevented a user from ever clearing a VLESS flow via the panel.
FlowOverride on client_inbounds is the per-inbound mechanism that
already preserves flow correctly across protocols, so the guard
on the shared clients.flow column is the wrong place.
Drop the Flow guard, keep the rest (UUID/Password/Auth/Security/
Reverse — none of which have a per-inbound override column).
Adds a regression test that asserts clearing flow on the owning
inbound makes ListForInbound return flow="".
The existing cross-protocol test is rewritten to assert on the
user-visible behavior (ListForInbound flow) instead of the shared
clients.flow column.
fillProtocolDefaults only populates the credential relevant to the
inbound's protocol (c.ID for VLESS, c.Auth for Hysteria, c.Password
for Trojan/Shadowsocks). Each inbound's settings.clients JSON
therefore carries the same client with only one of those fields set.
SyncInbound's update path was unconditionally copying every credential
column from incoming to the existing clients row, so the second sync
(e.g. Hysteria after VLESS) would write UUID="" over a valid VLESS
UUID and Auth="" the other way around. The next GetXrayConfig then
emitted VLESS client entries with no "id" field, and xray-core
crashed on startup with "common/uuid: invalid UUID:".
Guard UUID/Password/Auth/Flow/Security/Reverse against empty
overwrites so each protocol's sync only writes the credentials it
actually owns. Other fields (LimitIP, TotalGB, Comment, etc.) keep
the existing copy-everything behavior so admins can still clear them
through the panel.
Regression test in client_sync_multiprotocol_test.go.
Closes#4538