mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-02-10 11:40:28 +08:00
feat: subscription timeout settings & subscription update logic efficiency (#1562)
1. Add updateTimeout property to IProfileItem interface 2. Add dedicated timeout config option for subscriptions 3. Improve subscription update logic efficiency 4. Use subscriptionTimeout as the smart fallback time 5. Localize profiles.editInfo.updateTimeout and profiles.editInfo.updateTimeoutPlaceholder for en-US/fa-IR/ru-RU/zh-CN/zh-TW
This commit is contained in:
parent
767cdfeef3
commit
d3a23a0601
@ -218,10 +218,10 @@ async function fetchAndValidateSubscription(options: FetchOptions): Promise<Fetc
|
||||
|
||||
export async function createProfile(item: Partial<IProfileItem>): Promise<IProfileItem> {
|
||||
const id = item.id || new Date().getTime().toString(16)
|
||||
const newItem = {
|
||||
const newItem: IProfileItem = {
|
||||
id,
|
||||
name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'),
|
||||
type: item.type,
|
||||
type: item.type!,
|
||||
url: item.url,
|
||||
substore: item.substore || false,
|
||||
interval: item.interval || 0,
|
||||
@ -230,85 +230,66 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
|
||||
allowFixedInterval: item.allowFixedInterval || false,
|
||||
autoUpdate: item.autoUpdate ?? false,
|
||||
authToken: item.authToken,
|
||||
updated: new Date().getTime()
|
||||
} as IProfileItem
|
||||
updated: new Date().getTime(),
|
||||
updateTimeout: item.updateTimeout || 5
|
||||
}
|
||||
|
||||
switch (newItem.type) {
|
||||
case 'remote': {
|
||||
const { userAgent, subscriptionTimeout = 30000 } = await getAppConfig()
|
||||
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
||||
if (!item.url) throw new Error('Empty URL')
|
||||
// Local
|
||||
if (newItem.type === 'local') {
|
||||
await setProfileStr(id, item.file || '')
|
||||
return newItem
|
||||
}
|
||||
|
||||
const baseOptions: Omit<FetchOptions, 'useProxy' | 'timeout'> = {
|
||||
url: item.url,
|
||||
mixedPort,
|
||||
userAgent: userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`,
|
||||
authToken: item.authToken,
|
||||
substore: newItem.substore || false
|
||||
}
|
||||
// Remote
|
||||
if (!item.url) throw new Error('Empty URL')
|
||||
|
||||
let result: FetchResult
|
||||
let finalUseProxy = newItem.useProxy
|
||||
const { userAgent, subscriptionTimeout = 30000 } = await getAppConfig()
|
||||
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
||||
const userItemTimeoutMs = (newItem.updateTimeout || 5) * 1000
|
||||
|
||||
if (newItem.useProxy) {
|
||||
result = await fetchAndValidateSubscription({
|
||||
...baseOptions,
|
||||
useProxy: true,
|
||||
timeout: subscriptionTimeout
|
||||
})
|
||||
} else if (newItem.substore) {
|
||||
// SubStore requests (especially collections) need more time as they fetch and merge multiple subscriptions
|
||||
// Use the full subscriptionTimeout since SubStore is a local server and doesn't need smart fallback
|
||||
result = await fetchAndValidateSubscription({
|
||||
...baseOptions,
|
||||
useProxy: false,
|
||||
timeout: subscriptionTimeout
|
||||
})
|
||||
} else {
|
||||
const smartTimeout = 5000
|
||||
try {
|
||||
result = await fetchAndValidateSubscription({
|
||||
...baseOptions,
|
||||
useProxy: false,
|
||||
timeout: smartTimeout
|
||||
})
|
||||
} catch (directError) {
|
||||
try {
|
||||
result = await fetchAndValidateSubscription({
|
||||
...baseOptions,
|
||||
useProxy: true,
|
||||
timeout: smartTimeout
|
||||
})
|
||||
finalUseProxy = true
|
||||
} catch {
|
||||
throw directError
|
||||
}
|
||||
}
|
||||
}
|
||||
const baseOptions: Omit<FetchOptions, 'useProxy' | 'timeout'> = {
|
||||
url: item.url,
|
||||
mixedPort,
|
||||
userAgent: userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`,
|
||||
authToken: item.authToken,
|
||||
substore: newItem.substore || false
|
||||
}
|
||||
|
||||
newItem.useProxy = finalUseProxy
|
||||
const { data, headers } = result
|
||||
const fetchSub = (useProxy: boolean, timeout: number) =>
|
||||
fetchAndValidateSubscription({ ...baseOptions, useProxy, timeout })
|
||||
|
||||
if (headers['content-disposition'] && newItem.name === 'Remote File') {
|
||||
newItem.name = parseFilename(headers['content-disposition'])
|
||||
let result: FetchResult
|
||||
if (newItem.useProxy || newItem.substore) {
|
||||
result = await fetchSub(newItem.useProxy!, userItemTimeoutMs)
|
||||
} else {
|
||||
try {
|
||||
result = await fetchSub(false, userItemTimeoutMs)
|
||||
} catch (directError) {
|
||||
try {
|
||||
// smart fallback
|
||||
result = await fetchSub(true, subscriptionTimeout)
|
||||
} catch {
|
||||
throw directError
|
||||
}
|
||||
if (headers['profile-web-page-url']) {
|
||||
newItem.home = headers['profile-web-page-url']
|
||||
}
|
||||
if (headers['profile-update-interval'] && !item.allowFixedInterval) {
|
||||
newItem.interval = parseInt(headers['profile-update-interval']) * 60
|
||||
}
|
||||
if (headers['subscription-userinfo']) {
|
||||
newItem.extra = parseSubinfo(headers['subscription-userinfo'])
|
||||
}
|
||||
await setProfileStr(id, data)
|
||||
break
|
||||
}
|
||||
case 'local': {
|
||||
await setProfileStr(id, item.file || '')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const { data, headers } = result
|
||||
|
||||
if (headers['content-disposition'] && newItem.name === 'Remote File') {
|
||||
newItem.name = parseFilename(headers['content-disposition'])
|
||||
}
|
||||
if (headers['profile-web-page-url']) {
|
||||
newItem.home = headers['profile-web-page-url']
|
||||
}
|
||||
if (headers['profile-update-interval'] && !item.allowFixedInterval) {
|
||||
newItem.interval = parseInt(headers['profile-update-interval']) * 60
|
||||
}
|
||||
if (headers['subscription-userinfo']) {
|
||||
newItem.extra = parseSubinfo(headers['subscription-userinfo'])
|
||||
}
|
||||
|
||||
await setProfileStr(id, data)
|
||||
return newItem
|
||||
}
|
||||
|
||||
|
||||
@ -32,7 +32,10 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
||||
const { item, updateProfileItem, onClose } = props
|
||||
const { overrideConfig } = useOverrideConfig()
|
||||
const { items: overrideItems = [] } = overrideConfig || {}
|
||||
const [values, setValues] = useState(item)
|
||||
const [values, setValues] = useState({
|
||||
...item,
|
||||
updateTimeout: item.updateTimeout ?? 5
|
||||
})
|
||||
const inputWidth = 'w-[400px] md:w-[400px] lg:w-[600px] xl:w-[800px]'
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -40,6 +43,7 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
||||
try {
|
||||
const updatedItem = {
|
||||
...values,
|
||||
updateTimeout: values.updateTimeout ?? 5,
|
||||
override: values.override?.filter(
|
||||
(i) =>
|
||||
overrideItems.find((t) => t.id === i) && !overrideItems.find((t) => t.id === i)?.global
|
||||
@ -192,6 +196,24 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<SettingItem title={t('profiles.editInfo.updateTimeout')}>
|
||||
<Input
|
||||
size="sm"
|
||||
type="text"
|
||||
className={cn(inputWidth)}
|
||||
value={values.updateTimeout?.toString() ?? ''}
|
||||
onValueChange={(v) => {
|
||||
if (v === '') {
|
||||
setValues({ ...values, updateTimeout: undefined as unknown as number })
|
||||
return
|
||||
}
|
||||
if (/^\d+$/.test(v)) {
|
||||
setValues({ ...values, updateTimeout: parseInt(v, 10) })
|
||||
}
|
||||
}}
|
||||
placeholder={t('profiles.editInfo.updateTimeoutPlaceholder')}
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem title={t('profiles.editInfo.override.title')}>
|
||||
<div>
|
||||
{overrideItems
|
||||
|
||||
@ -484,6 +484,8 @@
|
||||
"profiles.editInfo.override.global": "Global",
|
||||
"profiles.editInfo.override.noAvailable": "No available overrides",
|
||||
"profiles.editInfo.override.add": "Add Override",
|
||||
"profiles.editInfo.updateTimeout": "Update Timeout",
|
||||
"profiles.editInfo.updateTimeoutPlaceholder": "Timeout in seconds",
|
||||
"profiles.editFile.title": "Edit Profile",
|
||||
"profiles.editFile.notice": "Note: Changes made here will be reset after profile update. For custom configurations, please use",
|
||||
"profiles.editFile.override": "Override",
|
||||
|
||||
@ -454,6 +454,8 @@
|
||||
"profiles.editInfo.override.global": "جهانی",
|
||||
"profiles.editInfo.override.noAvailable": "جایگزینیای در دسترس نیست",
|
||||
"profiles.editInfo.override.add": "افزودن جایگزینی",
|
||||
"profiles.editInfo.updateTimeout": "زمان انتظار بهروزرسانی",
|
||||
"profiles.editInfo.updateTimeoutPlaceholder": "زمان پایان دادن به ثانیه",
|
||||
"profiles.editInfo.intervalPlaceholder": "مثال: 30 یا '0 * * * *'",
|
||||
"profiles.editInfo.intervalInvalid": "نامعتبر",
|
||||
"profiles.editInfo.intervalMinutes": "فاصله زمانی ثابت به دقیقه",
|
||||
|
||||
@ -454,6 +454,8 @@
|
||||
"profiles.editInfo.override.global": "Глобальный",
|
||||
"profiles.editInfo.override.noAvailable": "Нет доступных переопределений",
|
||||
"profiles.editInfo.override.add": "Добавить переопределение",
|
||||
"profiles.editInfo.updateTimeout": "Таймаут обновления",
|
||||
"profiles.editInfo.updateTimeoutPlaceholder": "Время ожидания в секундах",
|
||||
"profiles.editInfo.intervalPlaceholder": "например: 30 или '0 * * * *'",
|
||||
"profiles.editInfo.intervalInvalid": "Недействительно",
|
||||
"profiles.editInfo.intervalMinutes": "Фиксированный интервал в минутах",
|
||||
|
||||
@ -489,6 +489,8 @@
|
||||
"profiles.editInfo.override.global": "全局",
|
||||
"profiles.editInfo.override.noAvailable": "没有可用的覆写",
|
||||
"profiles.editInfo.override.add": "添加覆写",
|
||||
"profiles.editInfo.updateTimeout": "更新超时时间",
|
||||
"profiles.editInfo.updateTimeoutPlaceholder": "以秒为单位的超时时间",
|
||||
"profiles.editFile.title": "编辑订阅",
|
||||
"profiles.editFile.notice": "注意:此处编辑配置更新订阅后会还原,如需要自定义配置请使用",
|
||||
"profiles.editFile.override": "覆写",
|
||||
|
||||
@ -489,6 +489,8 @@
|
||||
"profiles.editInfo.override.global": "全局",
|
||||
"profiles.editInfo.override.noAvailable": "沒有可用的覆寫",
|
||||
"profiles.editInfo.override.add": "添加覆寫",
|
||||
"profiles.editInfo.updateTimeout": "更新逾時時間",
|
||||
"profiles.editInfo.updateTimeoutPlaceholder": "以秒為單位的超時時間",
|
||||
"profiles.editFile.title": "編輯訂閱",
|
||||
"profiles.editFile.notice": "注意:此處編輯配置更新訂閱後會還原,如需要自定義配置請使用",
|
||||
"profiles.editFile.override": "覆寫",
|
||||
|
||||
1
src/shared/types.d.ts
vendored
1
src/shared/types.d.ts
vendored
@ -497,6 +497,7 @@ interface IProfileItem {
|
||||
allowFixedInterval?: boolean
|
||||
autoUpdate?: boolean
|
||||
authToken?: string
|
||||
updateTimeout?: number
|
||||
}
|
||||
|
||||
interface ISubStoreSub {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user