diff --git a/frontend/src/lib/xray/inbound-form-adapter.ts b/frontend/src/lib/xray/inbound-form-adapter.ts index 07b3c75a5..2d19278c8 100644 --- a/frontend/src/lib/xray/inbound-form-adapter.ts +++ b/frontend/src/lib/xray/inbound-form-adapter.ts @@ -11,6 +11,7 @@ import type { StreamSettings } from '@/schemas/api/inbound'; import type { Sniffing } from '@/schemas/primitives'; import type { z } from 'zod'; import { normalizeStreamSettingsForWire } from '@/lib/xray/stream-wire-normalize'; +import { canEnableSniffing } from '@/lib/xray/protocol-capabilities'; // Plain-data adapter between the panel's stored inbound row shape and // the typed InboundFormValues that Form.useForm carries inside @@ -302,7 +303,9 @@ export function formValuesToWirePayload(values: InboundFormValues): WireInboundP protocol: values.protocol, settings: JSON.stringify(settingsPruned), streamSettings: streamPruned ? JSON.stringify(streamPruned) : '', - sniffing: JSON.stringify(normalizeSniffing(values.sniffing)), + // mtproto is mtg-served, not Xray, so sniffing never applies — emit empty + // rather than the default { enabled: false } so the row carries no sniffing. + sniffing: canEnableSniffing({ protocol: values.protocol }) ? JSON.stringify(normalizeSniffing(values.sniffing)) : '', tag: values.tag, }; if (values.nodeId != null) payload.nodeId = values.nodeId; diff --git a/frontend/src/lib/xray/protocol-capabilities.ts b/frontend/src/lib/xray/protocol-capabilities.ts index 417071dff..168199ba2 100644 --- a/frontend/src/lib/xray/protocol-capabilities.ts +++ b/frontend/src/lib/xray/protocol-capabilities.ts @@ -50,6 +50,12 @@ export function canEnableStream(values: { protocol: string }): boolean { return STREAM_PROTOCOLS.includes(values.protocol); } +// mtproto is served by an external mtg process, not Xray, so the Xray sniffing +// block does not apply to it. Every other inbound supports sniffing. +export function canEnableSniffing(values: { protocol: string }): boolean { + return values.protocol !== 'mtproto'; +} + // Vision seed applies only when XTLS Vision (TCP/TLS) flow is selected // AND at least one VLESS client uses the vision flow. Excludes UDP variant. export function canEnableVisionSeed(values: CapabilityVlessSlice): boolean { diff --git a/frontend/src/pages/inbounds/form/InboundFormModal.tsx b/frontend/src/pages/inbounds/form/InboundFormModal.tsx index 1b0df6ccb..952d50208 100644 --- a/frontend/src/pages/inbounds/form/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/form/InboundFormModal.tsx @@ -23,6 +23,7 @@ import { createDefaultInboundSettings } from '@/lib/xray/inbound-defaults'; import { composeInboundTag, isAutoInboundTag, type InboundTagInput } from '@/lib/xray/inbound-tag'; import { canEnableReality, + canEnableSniffing, canEnableStream, canEnableTls, isSS2022, @@ -160,6 +161,7 @@ export default function InboundFormModal({ const network = Form.useWatch(['streamSettings', 'network'], form) ?? ''; const security = Form.useWatch(['streamSettings', 'security'], form) ?? 'none'; const streamEnabled = canEnableStream({ protocol }); + const sniffingSupported = canEnableSniffing({ protocol }); const wPort = Form.useWatch('port', form); const wListen = (Form.useWatch('listen', form) ?? '') as string; @@ -776,7 +778,7 @@ export default function InboundFormModal({
{t('pages.inbounds.advanced.allHelp')}
- + ), }, @@ -820,25 +822,27 @@ export default function InboundFormModal({ ), }] : []), - { - key: 'sniffing', - label: t('pages.inbounds.advanced.sniffing'), - children: ( - <> -
- {t('pages.inbounds.advanced.sniffingHelp')}{' '} - {'{ sniffing: { ... } }'}. -
- - - ), - }, + ...(sniffingSupported + ? [{ + key: 'sniffing', + label: t('pages.inbounds.advanced.sniffing'), + children: ( + <> +
+ {t('pages.inbounds.advanced.sniffingHelp')}{' '} + {'{ sniffing: { ... } }'}. +
+ + + ), + }] + : []), ]} /> @@ -896,7 +900,9 @@ export default function InboundFormModal({ { key: 'security', label: t('pages.inbounds.securityTab'), children: securityTab, forceRender: true }, ] : []), - { key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab, forceRender: true }, + ...(sniffingSupported + ? [{ key: 'sniffing', label: t('pages.inbounds.sniffingTab'), children: sniffingTab, forceRender: true }] + : []), { key: 'advanced', label: t('pages.xray.advancedTemplate'), children: advancedTab, forceRender: true }, ]} /> diff --git a/frontend/src/pages/inbounds/form/advanced-editors.tsx b/frontend/src/pages/inbounds/form/advanced-editors.tsx index 3dbd312da..788fc7ac1 100644 --- a/frontend/src/pages/inbounds/form/advanced-editors.tsx +++ b/frontend/src/pages/inbounds/form/advanced-editors.tsx @@ -92,9 +92,11 @@ export function AdvancedSliceEditor({ export function AdvancedAllEditor({ form, streamEnabled, + sniffingEnabled, }: { form: FormInstance; streamEnabled: boolean; + sniffingEnabled: boolean; }) { // preserve: true — default useWatch returns only registered fields, so // sub-trees we never bound (settings.clients/fallbacks, sniffing @@ -127,8 +129,10 @@ export function AdvancedAllEditor({ protocol: wProtocol ?? '', tag: wTag ?? '', settings: settingsView, - sniffing: normalizeSniffing(wSniffing as Parameters[0]), }; + if (sniffingEnabled) { + out.sniffing = normalizeSniffing(wSniffing as Parameters[0]); + } if (streamView) out.streamSettings = streamView; return JSON.stringify(out, null, 2); }; @@ -146,7 +150,7 @@ export function AdvancedAllEditor({ setText(formStr); lastEmitRef.current = formStr; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [wListen, wPort, wProtocol, wTag, wSettings, wSniffing, wStream, streamEnabled]); + }, [wListen, wPort, wProtocol, wTag, wSettings, wSniffing, wStream, streamEnabled, sniffingEnabled]); return ( - - + + + + + + + + + + + + + +