fix: restore XHTTP/gRPC/KCP transport settings in Compose server UI (#5995)

The Compose UI migration (ServerUiState + BaseServerActivity) dropped or
misbound several transport-specific fields that the legacy editor handled
correctly:

1. XHTTP mode (auto/packet-up/stream-up/stream-one) — stored in
   ProfileItem.xhttpMode, but ServerUiState had no field and the dropdown
   was bound to state.headerType (default "none")
2. gRPC mode (gun/multi) — stored in ProfileItem.mode, same problem
3. gRPC serviceName/authority — the "gRPC Authority" field was bound to
   state.host and "gRPC serviceName" to state.path, but the core outbound
   builder reads ProfileItem.authority/serviceName. Editing + saving a
   gRPC node wrote to host/path, breaking the connection
4. KCP seed — no field in ServerUiState, no input in the Compose UI

Changes (2 files):

ServerUiState.kt:
- Added constructor params + mutableState for: mode, xhttpMode,
  serviceName, authority, seed
- fromProfileItem(): read all five from initialConfig
- toProfileItem(): write all five with .nullIfBlank()

BaseServerActivity.kt CommonNetworkFields:
- Mode dropdown dispatches by network: GRPC -> state.mode,
  XHTTP -> state.xhttpMode, else -> state.headerType (TCP/KCP unchanged)
- Host/Authority field: GRPC -> state.authority, else -> state.host
- Path/serviceName field: GRPC -> state.serviceName, else -> state.path
- KCP: added seed text field, hide the path field (KCP doesn't use path;
  previously it showed a second "kcp seed" label bound to the wrong field)

Null handling is safe: XhttpSettingsBean.mode is nullable and Gson omits
null fields, so xray-core falls back to its default mode; gRPC
multiMode = (mode == "multi") is null-safe (false -> default gun).

Fixes #5993

Signed-off-by: dkgks

Signed-off-by: dkgks
Co-authored-by: root <root@debianG>
This commit is contained in:
dkgksandroot authored and GitHub committed 2026-08-02 10:41:49 +08:00
1 parent 0cf95ac8a3
commit c8f78621bf
2 files changed
+55 -19

No files matched your search

@@ -154,9 +154,19 @@ abstract class BaseServerActivity : BaseComponentActivity() {
else -> R.string.server_lab_head_type
}
),
state.headerType,
when (state.network) {
NetworkType.GRPC.type -> state.mode
NetworkType.XHTTP.type -> state.xhttpMode
else -> state.headerType
},
headerOptions,
{ state.headerType = it }
{
when (state.network) {
NetworkType.GRPC.type -> state.mode = it
NetworkType.XHTTP.type -> state.xhttpMode = it
else -> state.headerType = it
}
}
)
}
@@ -173,25 +183,26 @@ abstract class BaseServerActivity : BaseComponentActivity() {
else -> R.string.server_lab_request_host6
}
),
state.host,
{ state.host = it }
if (state.network == NetworkType.GRPC.type) state.authority else state.host,
{ if (state.network == NetworkType.GRPC.type) state.authority = it else state.host = it }
)
FormTextField(
stringResource(
when (state.network) {
NetworkType.KCP.type -> R.string.server_lab_path_kcp
NetworkType.WS.type -> R.string.server_lab_path_ws
NetworkType.HTTP_UPGRADE.type -> R.string.server_lab_path_httpupgrade
NetworkType.XHTTP.type -> R.string.server_lab_path_xhttp
NetworkType.H2.type -> R.string.server_lab_path_h2
NetworkType.GRPC.type -> R.string.server_lab_path_grpc
else -> R.string.server_lab_path
}
),
state.path,
{ state.path = it }
)
if (state.network != NetworkType.KCP.type) {
FormTextField(
stringResource(
when (state.network) {
NetworkType.WS.type -> R.string.server_lab_path_ws
NetworkType.HTTP_UPGRADE.type -> R.string.server_lab_path_httpupgrade
NetworkType.XHTTP.type -> R.string.server_lab_path_xhttp
NetworkType.H2.type -> R.string.server_lab_path_h2
NetworkType.GRPC.type -> R.string.server_lab_path_grpc
else -> R.string.server_lab_path
}
),
if (state.network == NetworkType.GRPC.type) state.serviceName else state.path,
{ if (state.network == NetworkType.GRPC.type) state.serviceName = it else state.path = it }
)
}
if (state.network == NetworkType.XHTTP.type) {
FormTextField(
@@ -201,6 +212,11 @@ abstract class BaseServerActivity : BaseComponentActivity() {
)
}
if (state.network == NetworkType.KCP.type) {
FormTextField(
stringResource(R.string.server_lab_path_kcp),
state.seed,
{ state.seed = it }
)
FormTextField(
stringResource(R.string.server_lab_kcp_mtu),
state.kcpMtu,
@@ -37,10 +37,15 @@ class ServerUiState(
bandwidthUp: String = "",
network: String = NetworkType.TCP.type,
headerType: String = "none",
mode: String = "",
xhttpMode: String = "",
serviceName: String = "",
authority: String = "",
host: String = "",
path: String = "",
xhttpExtra: String = "",
finalMask: String = "",
seed: String = "",
kcpMtu: String = "",
kcpTti: String = "",
browserDialerMode: String = "",
@@ -80,10 +85,15 @@ class ServerUiState(
var bandwidthUp by mutableStateOf(bandwidthUp)
var network by mutableStateOf(network)
var headerType by mutableStateOf(headerType)
var mode by mutableStateOf(mode)
var xhttpMode by mutableStateOf(xhttpMode)
var serviceName by mutableStateOf(serviceName)
var authority by mutableStateOf(authority)
var host by mutableStateOf(host)
var path by mutableStateOf(path)
var xhttpExtra by mutableStateOf(xhttpExtra)
var finalMask by mutableStateOf(finalMask)
var seed by mutableStateOf(seed)
var kcpMtu by mutableStateOf(kcpMtu)
var kcpTti by mutableStateOf(kcpTti)
var browserDialerMode by mutableStateOf(browserDialerMode)
@@ -139,10 +149,15 @@ class ServerUiState(
bandwidthUp = if (isHysteria2) bandwidthUp else null,
network = network,
headerType = headerType,
mode = mode.nullIfBlank(),
xhttpMode = xhttpMode.nullIfBlank(),
serviceName = serviceName.nullIfBlank(),
authority = authority.nullIfBlank(),
host = host,
path = path,
xhttpExtra = xhttpExtra.nullIfBlank(),
finalMask = finalMask.nullIfBlank(),
seed = seed.nullIfBlank(),
kcpMtu = kcpMtu.toIntOrNull(),
kcpTti = kcpTti.toIntOrNull(),
browserDialerMode = if (network in listOf(NetworkType.WS.type, NetworkType.XHTTP.type)) {
@@ -191,10 +206,15 @@ class ServerUiState(
bandwidthUp = initialConfig.bandwidthUp ?: "",
network = initialConfig.network ?: NetworkType.TCP.type,
headerType = initialConfig.headerType ?: "none",
mode = initialConfig.mode ?: "",
xhttpMode = initialConfig.xhttpMode ?: "",
serviceName = initialConfig.serviceName ?: "",
authority = initialConfig.authority ?: "",
host = initialConfig.host ?: "",
path = initialConfig.path ?: "",
xhttpExtra = initialConfig.xhttpExtra ?: "",
finalMask = initialConfig.finalMask ?: "",
seed = initialConfig.seed ?: "",
kcpMtu = initialConfig.kcpMtu?.toString() ?: "",
kcpTti = initialConfig.kcpTti?.toString() ?: "",
browserDialerMode = initialConfig.browserDialerMode ?: "",