import { Button, Divider, Form, Input, InputNumber, Select, Space, Switch } from 'antd'; import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons'; import type { FormInstance } from 'antd/es/form'; import type { NamePath } from 'antd/es/form/interface'; import { RandomUtil } from '@/utils'; import { OutboundProtocols } from '@/schemas/primitives'; // Pattern A FinalMaskForm. Renders a Fragment of Form.Items at absolute // paths under `name`; the parent modal owns the Form instance. // // Naming convention inside Form.List: AntD prefixes Form.Item `name` // with the Form.List's own `name`. So Form.Items inside the render // prop use RELATIVE paths (e.g. `[field.name, 'type']`). Nested // Form.Lists also use relative names. Using absolute paths here would // double up the prefix and silently route reads/writes to the wrong // storage path. export interface FinalMaskFormProps { name: NamePath; network: string; protocol: string; form: FormInstance; } const TCP_NETWORKS = ['raw', 'tcp', 'httpupgrade', 'ws', 'grpc', 'xhttp']; function asPath(name: NamePath): (string | number)[] { return Array.isArray(name) ? [...name] : [name]; } function defaultTcpMaskSettings(type: string): Record { switch (type) { case 'fragment': return { packets: '1-3', length: '', delay: '', maxSplit: '' }; case 'sudoku': return { password: '', ascii: '', customTable: '', customTables: '', paddingMin: 0, paddingMax: 0, }; case 'header-custom': return { clients: [], servers: [] }; default: return {}; } } function defaultUdpMaskSettings(type: string): Record { switch (type) { case 'salamander': case 'mkcp-aes128gcm': return { password: '' }; case 'header-dns': return { domain: '' }; case 'xdns': return { domains: [] }; case 'xicmp': return { ip: '0.0.0.0', id: 0 }; case 'header-custom': return { client: [], server: [] }; case 'noise': return { reset: 0, noise: [] }; default: return {}; } } function defaultClientServerItem(): Record { return { delay: 0, rand: 0, randRange: '0-255', type: 'array', packet: [] }; } function defaultUdpClientServerItem(): Record { return { rand: 0, randRange: '0-255', type: 'array', packet: [] }; } function defaultNoiseItem(): Record { return { rand: '1-8192', randRange: '0-255', type: 'array', packet: [], delay: '10-20', }; } function defaultQuicParams(): Record { return { congestion: 'bbr', debug: false, maxIdleTimeout: 30, keepAlivePeriod: 10, disablePathMTUDiscovery: false, maxIncomingStreams: 1024, initStreamReceiveWindow: 8388608, maxStreamReceiveWindow: 8388608, initConnectionReceiveWindow: 20971520, maxConnectionReceiveWindow: 20971520, }; } function defaultUdpHop(): Record { return { ports: '20000-50000', interval: '5-10' }; } export default function FinalMaskForm({ name, network, protocol, form }: FinalMaskFormProps) { const base = asPath(name); const isHysteria = protocol === OutboundProtocols.Hysteria || protocol === 'hysteria'; const showTcp = TCP_NETWORKS.includes(network); const showUdp = isHysteria || network === 'kcp'; const showQuic = isHysteria || network === 'xhttp'; const quicParams = Form.useWatch([...base, 'quicParams'], { form, preserve: true }); const hasQuicParams = quicParams != null; if (!showTcp && !showUdp && !showQuic) return null; return ( <> {showTcp && } {showUdp && } {showQuic && ( <> { form.setFieldValue([...base, 'quicParams'], v ? defaultQuicParams() : undefined); }} /> {hasQuicParams && } )} ); } function TcpMasksList({ base, form }: { base: (string | number)[]; form: FormInstance }) { return ( {(fields, { add, remove }) => ( <>