Skip to content

Commit

Permalink
feat: add configure hysteria2 modal (daeuniverse#440)
Browse files Browse the repository at this point in the history
  • Loading branch information
wanlce authored Jun 16, 2024
1 parent d1eb008 commit ef2b4c5
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 0 deletions.
58 changes: 58 additions & 0 deletions src/components/ConfigureNodeFormModal/Hysteria2Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Checkbox, NumberInput, TextInput } from '@mantine/core'
import { useForm, zodResolver } from '@mantine/form'
import { useTranslation } from 'react-i18next'
import { z } from 'zod'

import { FormActions } from '~/components/FormActions'
import { DEFAULT_HYSTERIA2_FORM_VALUES, hysteria2Schema } from '~/constants'
import { generateHysteria2URL } from '~/utils'

export const Hysteria2Form = ({ onLinkGeneration }: { onLinkGeneration: (link: string) => void }) => {
const { t } = useTranslation()
const { onSubmit, getInputProps, reset } = useForm<z.infer<typeof hysteria2Schema>>({
initialValues: DEFAULT_HYSTERIA2_FORM_VALUES,
validate: zodResolver(hysteria2Schema),
})

const handleSubmit = onSubmit((values) => {
/* hysteria2://[auth@]hostname[:port]/?[key=value]&[key=value]... */
const query = {
obfs: values.obfs,
obfsPassword: values.obfsPassword,
sni: values.sni,
insecure: values.allowInsecure,
pinSHA256: values.pinSHA256,
}

return onLinkGeneration(
generateHysteria2URL({
protocol: 'hysteria2',
auth: values.auth,
host: values.server,
port: values.port,
params: query,
}),
)
})

return (
<form onSubmit={handleSubmit}>
<TextInput label={t('configureNode.name')} {...getInputProps('name')} />
<TextInput label={t('configureNode.host')} withAsterisk {...getInputProps('server')} />
<NumberInput label={t('configureNode.port')} withAsterisk min={0} max={65535} {...getInputProps('port')} />
<TextInput label="Auth" withAsterisk {...getInputProps('auth')} />
{/* The obfuscation feature has not been implemented yet
<Select
label={t('configureNode.obfs')}
data={[{ label: 'salamander', value: 'salamander' }]}
{...getInputProps('obfs')}
/>
<TextInput label={t('configureNode.obfsPassword')} {...getInputProps('obfsPassword')} />
*/}
<TextInput label="SNI" {...getInputProps('sni')} />
<TextInput label="Pin SHA256" {...getInputProps('pinSHA256')} />
<Checkbox label={t('allowInsecure')} {...getInputProps('allowInsecure', { type: 'checkbox' })} />
<FormActions reset={reset} />
</form>
)
}
8 changes: 8 additions & 0 deletions src/components/ConfigureNodeFormModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { z } from 'zod'
import { useImportNodesMutation } from '~/apis'

import { HTTPForm } from './HTTPForm'
import { Hysteria2Form } from './Hysteria2Form'
import { JuicityForm } from './JuicityForm'
import { SSForm } from './SSForm'
import { SSRForm } from './SSRForm'
Expand Down Expand Up @@ -64,6 +65,7 @@ export const ConfigureNodeFormModal = ({ opened, onClose }: { opened: boolean; o
<Tabs.Tab value="ssr">SSR</Tabs.Tab>
<Tabs.Tab value="trojan">Trojan</Tabs.Tab>
<Tabs.Tab value="juicity">Juicity</Tabs.Tab>
<Tabs.Tab value="hysteria2">Hysteria2</Tabs.Tab>
<Tabs.Tab value="tuic">Tuic</Tabs.Tab>
<Tabs.Tab value="http">HTTP</Tabs.Tab>
<Tabs.Tab value="socks5">SOCKS5</Tabs.Tab>
Expand Down Expand Up @@ -99,6 +101,12 @@ export const ConfigureNodeFormModal = ({ opened, onClose }: { opened: boolean; o
</Stack>
</Tabs.Panel>

<Tabs.Panel value="hysteria2">
<Stack>
<Hysteria2Form onLinkGeneration={onLinkGeneration} />
</Stack>
</Tabs.Panel>

<Tabs.Panel value="tuic">
<Stack>
<TuicForm onLinkGeneration={onLinkGeneration} />
Expand Down
13 changes: 13 additions & 0 deletions src/constants/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { GlobalInput, Policy } from '~/schemas/gql/graphql'
import { DialMode, LogLevel, TLSImplementation, TcpCheckHttpMethod, UTLSImitate } from './misc'
import {
httpSchema,
hysteria2Schema,
juicitySchema,
socks5Schema,
ssSchema,
Expand Down Expand Up @@ -169,6 +170,18 @@ export const DEFAULT_JUICITY_FORM_VALUES: z.infer<typeof juicitySchema> = {
sni: '',
}

export const DEFAULT_HYSTERIA2_FORM_VALUES: z.infer<typeof hysteria2Schema> = {
name: '',
port: 443,
server: '',
auth: '',
obfs: '',
obfsPassword: '',
sni: '',
allowInsecure: false,
pinSHA256: '',
}

export const DEFAULT_HTTP_FORM_VALUES: z.infer<typeof httpSchema> = {
host: '',
name: '',
Expand Down
12 changes: 12 additions & 0 deletions src/constants/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,18 @@ export const juicitySchema = z.object({
congestion_control: z.string(),
})

export const hysteria2Schema = z.object({
name: z.string(),
server: z.string().nonempty(),
port: z.number().min(0).max(65535),
auth: z.string(),
obfs: z.string(),
obfsPassword: z.string(),
sni: z.string(),
allowInsecure: z.boolean(),
pinSHA256: z.string(),
})

export const httpSchema = z.object({
username: z.string(),
password: z.string(),
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"noObfuscation": "No obfuscation",
"obfs": "Obfuscation",
"obfsParam": "Obfs Param",
"obfsPassword": "Obfs Password",
"origin": "origin",
"password": "Password",
"path": "Path",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"noObfuscation": "不伪装",
"obfs": "混淆",
"obfsParam": "混淆参数",
"obfsPassword": "混淆密码",
"origin": "原版",
"password": "密码",
"path": "路径",
Expand Down
26 changes: 26 additions & 0 deletions src/utils/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,29 @@ export const generateURL = ({ username, password, protocol, host, port, params,

return uri.toString()
}

export const generateHysteria2URL = ({
protocol,
auth,
host,
port,
params,
}: {
protocol: string
auth: string
host: string
port: number
params: Record<string, string | number | boolean>
}) => {
// Encode the auth field to handle special characters like '@'
const encodedAuth = encodeURIComponent(auth)
const uri = new URL(`${protocol}://${encodedAuth}@${host}:${port}/`)

Object.entries(params).forEach(([key, value]) => {
if (value !== null && value !== undefined && value !== '') {
uri.searchParams.append(key, String(value))
}
})

return uri.toString()
}

0 comments on commit ef2b4c5

Please sign in to comment.