fix socks version issue
This commit is contained in:
@@ -26,6 +26,12 @@ enum SingboxFieldKind {
|
||||
port,
|
||||
boolean,
|
||||
stringList,
|
||||
|
||||
/// A value constrained to [SingboxProxyFormField.allowedValues] and always
|
||||
/// serialized as a JSON string. Use for sing-box string enums such as the
|
||||
/// SOCKS `version` field, whose accepted values ("4", "4a", "5") are
|
||||
/// string-typed even when numeric-looking.
|
||||
choice,
|
||||
}
|
||||
|
||||
class SingboxProxyFormField {
|
||||
@@ -39,6 +45,10 @@ class SingboxProxyFormField {
|
||||
final int? minValue;
|
||||
final int? maxValue;
|
||||
|
||||
/// Permitted values for a [SingboxFieldKind.choice] field, rendered as a
|
||||
/// dropdown and enforced during validation.
|
||||
final List<String>? allowedValues;
|
||||
|
||||
const SingboxProxyFormField({
|
||||
required this.key,
|
||||
required this.label,
|
||||
@@ -49,6 +59,7 @@ class SingboxProxyFormField {
|
||||
this.exactListLength,
|
||||
this.minValue,
|
||||
this.maxValue,
|
||||
this.allowedValues,
|
||||
});
|
||||
|
||||
bool get isSecret => kind == SingboxFieldKind.secret;
|
||||
@@ -58,6 +69,7 @@ class SingboxProxyFormField {
|
||||
bool get isPort => kind == SingboxFieldKind.port;
|
||||
bool get isBoolean => kind == SingboxFieldKind.boolean;
|
||||
bool get isStringList => kind == SingboxFieldKind.stringList;
|
||||
bool get isChoice => kind == SingboxFieldKind.choice;
|
||||
}
|
||||
|
||||
/// Shared parsing of boolean form values. Returns null when the text doesn't
|
||||
|
||||
@@ -72,6 +72,12 @@ class SingboxProxyFormSpec {
|
||||
if (field.isBoolean && value.isNotEmpty && parseFormBool(value) == null) {
|
||||
return '${field.label} must be true or false.';
|
||||
}
|
||||
if (field.isChoice && value.isNotEmpty) {
|
||||
final allowedValues = field.allowedValues;
|
||||
if (allowedValues != null && !allowedValues.contains(value)) {
|
||||
return '${field.label} must be one of: ${allowedValues.join(', ')}.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -139,7 +139,9 @@ const singboxProxyFormSpecs = <SingboxProxyProfileType, SingboxProxyFormSpec>{
|
||||
key: 'version',
|
||||
label: 'SOCKS Version',
|
||||
defaultValue: '5',
|
||||
kind: SingboxFieldKind.integer,
|
||||
// sing-box expects this as a JSON string enum, not a number.
|
||||
kind: SingboxFieldKind.choice,
|
||||
allowedValues: ['5', '4a', '4'],
|
||||
),
|
||||
_usernameField,
|
||||
_optionalPasswordField,
|
||||
|
||||
+47
@@ -117,6 +117,12 @@ class _SectionFields extends StatelessWidget {
|
||||
controller: controllers[field.key]!,
|
||||
onChanged: (value) => onChanged(field.key, value),
|
||||
)
|
||||
else if (field.isChoice)
|
||||
_ChoiceField(
|
||||
field: field,
|
||||
controller: controllers[field.key]!,
|
||||
onChanged: (value) => onChanged(field.key, value),
|
||||
)
|
||||
else if (field.isSecret)
|
||||
ObscurableTextField(
|
||||
controller: controllers[field.key],
|
||||
@@ -295,3 +301,44 @@ class _BooleanField extends HookWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChoiceField extends HookWidget {
|
||||
final SingboxProxyFormField field;
|
||||
final TextEditingController controller;
|
||||
final ValueChanged<String> onChanged;
|
||||
|
||||
const _ChoiceField({
|
||||
required this.field,
|
||||
required this.controller,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final allowedValues = field.allowedValues ?? const <String>[];
|
||||
final value = useListenableSelector(
|
||||
controller,
|
||||
() => allowedValues.contains(controller.text) ? controller.text : null,
|
||||
);
|
||||
|
||||
return DropdownButtonFormField<String>(
|
||||
initialValue: value,
|
||||
decoration: InputDecoration(
|
||||
labelText: field.required ? '${field.label} *' : field.label,
|
||||
helperText: field.helperText,
|
||||
helperMaxLines: 3,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: [
|
||||
for (final option in allowedValues)
|
||||
DropdownMenuItem(value: option, child: Text(option)),
|
||||
],
|
||||
onChanged: (selected) {
|
||||
if (selected != null) {
|
||||
controller.text = selected;
|
||||
onChanged(selected);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,34 @@ void main() {
|
||||
expect(secrets, {'password': 'secret'});
|
||||
});
|
||||
|
||||
test('serializes SOCKS version as a JSON string', () {
|
||||
final spec = singboxProxyFormSpecs[SingboxProxyProfileType.socks]!;
|
||||
final values = {
|
||||
'server': '127.0.0.1',
|
||||
'server_port': '9050',
|
||||
'version': '5',
|
||||
};
|
||||
|
||||
expect(spec.validate(values), isNull);
|
||||
|
||||
final config = jsonDecode(spec.toConfigJson(values));
|
||||
|
||||
// sing-box rejects a numeric version; it must be the string "5".
|
||||
expect(config['version'], '5');
|
||||
expect(config['version'], isA<String>());
|
||||
});
|
||||
|
||||
test('rejects an out-of-range SOCKS version', () {
|
||||
final spec = singboxProxyFormSpecs[SingboxProxyProfileType.socks]!;
|
||||
final values = {
|
||||
'server': '127.0.0.1',
|
||||
'server_port': '9050',
|
||||
'version': '6',
|
||||
};
|
||||
|
||||
expect(spec.validate(values), isNotNull);
|
||||
});
|
||||
|
||||
test('hydrates structured values from public and secret JSON', () {
|
||||
final spec = singboxProxyFormSpecs[SingboxProxyProfileType.vless]!;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user