User-reported vless share link with full xhttp + reality + finalmask
config failed to round-trip on outbound import. The inbound link
generator emits three payloads the outbound parser was ignoring:
1. `extra=<json>` — bundles advanced xhttp knobs (xPaddingBytes,
scMaxEachPostBytes, scMinPostsIntervalMs, padding-obfs keys,
etc.). applyXhttpStringFromParams now JSON.parses this and
merges the fields into xhttpSettings via the same JSON-branch
logic used by vmess.
2. `x_padding_bytes=<range>` — snake_case alias the inbound emits
alongside the camelCase form. Now applied before camelCase so
explicit `xPaddingBytes` URL params still win.
3. `fm=<json>` — full finalmask object including quicParams.udpHop
and tcp/udp mask arrays. New applyFinalMaskParam attaches the
decoded object to streamSettings.finalmask. Wired into both
parseVlessLink and parseTrojanLink.
Tests:
- Real B20 link parses with xhttp + reality + finalmask all populated
- Precedence: camelCase URL > extra JSON > snake_case alias > default
- Malformed extra JSON falls through without crashing the parser
300/300 pass.
Phase 2 smoke fixes on the Inbound add flow surfaced that hysteria2 was
modeled as a separate top-level protocol when it's really just hysteria
v2. The xray transports/hysteria.html docs also pin the hysteria stream
to a minimal shape (version/auth/udpIdleTimeout/masquerade) — the
previous schema carried legacy congestion/up/down/udphop/window knobs
that aren't part of the wire contract.
Hysteria2 removal:
- Drop 'hysteria2' from ProtocolSchema enum and Protocols const
- Drop hysteria2 branches from inbound/outbound discriminated unions
- Drop createDefaultHysteria2InboundSettings / OutboundSettings
- Delete schemas/protocols/inbound/hysteria2.ts and outbound/hysteria2.ts
- Drop hysteria2 case in getInboundClients / genLink (fell through to
the hysteria handler anyway)
- Update client form modals' MULTI_CLIENT_PROTOCOLS sets
- Remove hysteria2-basic fixture + snapshot entries (14 capability
cases, 1 protocols fixture, 1 inbound-defaults factory)
- Keep parseHysteria2Link() outbound parser since hysteria2:// is the
share-link URI prefix for hysteria v2
Hysteria stream alignment with xtls docs:
- HysteriaStreamSettingsSchema reduced to version/auth/udpIdleTimeout/
masquerade per transports/hysteria.html
- Masquerade type adds '' (default 404 page) and defaults to it
- Outbound form drops Congestion/Upload/Download/UDP hop/Max idle/
Keep alive/Disable Path MTU controls and the receive-window note
- newStreamSlice('hysteria') in OutboundFormModal mirrors the trimmed
shape; outbound-link-parser emits the trimmed shape too
- InboundFormModal Masquerade Select gains the default option
New TUN inbound schema:
- Add schemas/protocols/inbound/tun.ts with name/mtu/gateway/dns/
userLevel/autoSystemRoutingTable/autoOutboundsInterface
- Wire into ProtocolSchema enum, InboundSettingsSchema discriminated
union, createDefaultInboundSettings dispatcher
Other Phase 2 smoke fixes folded in:
- Tunnel portMap UI swaps Form.List for HeaderMapEditor v1 — wire
shape is Record<string,string> and the List was producing arrays
- Hysteria onValuesChange seeds full TLS schema defaults + one
empty certificate row (Cipher Suites/Min/Max Version/uTLS/ALPN
were undefined before)
- HTTP/Mixed accounts Add button auto-fills user/pass with
RandomUtil.randomLowerAndNum
- Hysteria security tab gates the 'none' radio out — TLS only
- Hysteria stream tab drops the inbound Auth password field (xray
inbound auth is per-user via 'users', not stream-level)
- Reality onSecurityChange auto-randomizes target/serverNames/
shortIds and fetches an X25519 keypair
- Tag and DB-side fields (up/down/total/expiryTime/
lastTrafficResetTime/clientStats/security) gain hidden Form.Items
so validateFields keeps them in the wire payload (rc-component
form strips unregistered fields)
- WireGuard inbound auto-seeds one peer with generated keypair,
allowedIPs ['10.0.0.2/32'], keepAlive 0 — matches legacy
- WireGuard peer rows separated by Divider with the Peer N title
and a small inline remove button (titlePlacement="center")
Extract the XHTTP key-mapping into typed string/number/bool key arrays
applied by both the URL query-param branch and the vmess JSON branch.
The parser now covers xPaddingObfsMode + xPaddingKey/Header/Placement/
Method, sessionKey/seqKey/uplinkData{Placement,Key}, noSSEHeader,
scMaxBufferedPosts, scStreamUpServerSecs, serverMaxHeaderBytes, and
uplinkHTTPMethod alongside the previous five XHTTP fields. Two new
round-trip tests cover the padding-obfs surface on both link forms.
Pick up xPaddingBytes, scMaxEachPostBytes, scMinPostsIntervalMs,
uplinkChunkSize, and noGRPCHeader from both vmess:// JSON and the URL
query-param parsers (vless/trojan). The advanced xmux/padding-obfs/
reality-shortId knobs still wait on a follow-up; this slice unblocks
the common case where a phone-issued xhttp link carries non-default
padding or post sizes.
The legacy outbound modal could import a vmess://, vless://, trojan://,
ss://, or hysteria2:// share link via a Convert button on the JSON
tab. Restore that UX with a focused pure-function parser.
lib/xray/outbound-link-parser.ts:
- parseVmessLink: base64 JSON, maps net/tls + per-network params onto
the discriminated stream branch.
- parseVlessLink: standard URL with type/security/sni/pbk/sid/fp/flow
query params, dispatches transport via buildStream + applies
security params via applySecurityParams.
- parseTrojanLink: same URL pattern, defaults security to tls.
- parseShadowsocksLink: both modern (base64 userinfo@host:port) and
legacy (base64 of whole thing) ss:// formats.
- parseHysteria2Link: accepts both hysteria2:// and hy2:// schemes,
uses the hysteria stream branch with version=2 + TLS h3.
- parseOutboundLink dispatcher returns the first non-null parser
result, or null when no scheme matches.
test/outbound-link-parser.test.ts:
- 13 cases covering happy paths for each protocol family plus malformed
input, ss:// dual-format handling, hy2:// alias.
OutboundFormModal.tsx:
- Import button on the JSON tab Input.Search; on success, parsed
payload flows through rawOutboundToFormValues, the form is reset,
and we switch back to the Basic tab.
- Tag is preserved when the parsed link does not carry one.
Out of scope: advanced fields the legacy parser handled (xmux, padding
obfs, reality short IDs, finalmask from fm= param). Power users can
finish the import in the form after the basics land.