feat(ui): allow custom fragment packets ranges, not just presets (#5075)

The fragment "packets" field was a locked dropdown (tlshello / 1-3 / 1-5)
in both the finalmask TCP-mask form and the Freedom outbound form, while
xray-core accepts any "n-m" packet range. Replace both with an
AutoComplete that keeps the presets as suggestions and validates free
input as "tlshello" or a numeric range.
This commit is contained in:
MHSanaei
2026-06-12 09:04:17 +02:00
parent 0e0e41197f
commit bade1fcef6
2 changed files with 33 additions and 6 deletions
@@ -1,4 +1,4 @@
import { Button, Divider, Form, Input, InputNumber, Select, Space, Switch } from 'antd'; import { AutoComplete, Button, Divider, Form, Input, InputNumber, Select, Space, Switch } from 'antd';
import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons'; import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons';
import type { FormInstance } from 'antd/es/form'; import type { FormInstance } from 'antd/es/form';
import type { NamePath } from 'antd/es/form/interface'; import type { NamePath } from 'antd/es/form/interface';
@@ -205,13 +205,18 @@ function TcpMaskItem({
if (type === 'fragment') { if (type === 'fragment') {
return ( return (
<> <>
<Form.Item label="Packets" name={[fieldName, 'settings', 'packets']}> <Form.Item
<Select label="Packets"
name={[fieldName, 'settings', 'packets']}
rules={[{ validator: validateFragmentPackets }]}
>
<AutoComplete
options={[ options={[
{ value: 'tlshello', label: 'tlshello' }, { value: 'tlshello', label: 'tlshello' },
{ value: '1-3', label: '1-3' }, { value: '1-3', label: '1-3' },
{ value: '1-5', label: '1-5' }, { value: '1-5', label: '1-5' },
]} ]}
placeholder="tlshello or n-m, e.g. 1-3"
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -264,6 +269,16 @@ function TcpMaskItem({
); );
} }
// xray's fragment `packets` accepts "tlshello" or an arbitrary packet-number
// range like "1-3" (#5075 — presets only covered the common cases).
function validateFragmentPackets(_rule: unknown, value: unknown): Promise<void> {
const str = typeof value === 'string' ? value.trim() : String(value ?? '').trim();
if (str.length === 0 || str === 'tlshello' || /^\d+-\d+$/.test(str)) {
return Promise.resolve();
}
return Promise.reject(new Error('Use "tlshello" or a packet range like 1-3'));
}
// Walks a deep object path safely. Used inside shouldUpdate which gets // Walks a deep object path safely. Used inside shouldUpdate which gets
// the whole form values blob; we need to compare a deep field across // the whole form values blob; we need to compare a deep field across
// prev/curr without crashing on missing intermediates. // prev/curr without crashing on missing intermediates.
@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button, Form, Input, InputNumber, Select, Switch, type FormInstance } from 'antd'; import { AutoComplete, Button, Form, Input, InputNumber, Select, Switch, type FormInstance } from 'antd';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { OutboundDomainStrategies } from '@/schemas/primitives'; import { OutboundDomainStrategies } from '@/schemas/primitives';
@@ -67,12 +67,24 @@ export default function FreedomFields({ form }: { form: FormInstance<OutboundFor
<Form.Item <Form.Item
label={t('pages.settings.subFormats.packets')} label={t('pages.settings.subFormats.packets')}
name={['settings', 'fragment', 'packets']} name={['settings', 'fragment', 'packets']}
rules={[{
validator: (_rule, value) => {
const str = String(value ?? '').trim();
// xray accepts "tlshello" or any packet-number range (#5075)
if (str === '' || str === 'tlshello' || /^\d+-\d+$/.test(str)) {
return Promise.resolve();
}
return Promise.reject(new Error('Use "tlshello" or a packet range like 1-3'));
},
}]}
> >
<Select <AutoComplete
options={[ options={[
{ value: '1-3', label: '1-3' },
{ value: 'tlshello', label: 'tlshello' }, { value: 'tlshello', label: 'tlshello' },
{ value: '1-3', label: '1-3' },
{ value: '1-5', label: '1-5' },
]} ]}
placeholder="tlshello or n-m, e.g. 1-3"
/> />
</Form.Item> </Form.Item>
<Form.Item label={t('pages.settings.subFormats.length')} name={['settings', 'fragment', 'length']}> <Form.Item label={t('pages.settings.subFormats.length')} name={['settings', 'fragment', 'length']}>