mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-02-11 04:00:32 +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> {
|
export async function createProfile(item: Partial<IProfileItem>): Promise<IProfileItem> {
|
||||||
const id = item.id || new Date().getTime().toString(16)
|
const id = item.id || new Date().getTime().toString(16)
|
||||||
const newItem = {
|
const newItem: IProfileItem = {
|
||||||
id,
|
id,
|
||||||
name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'),
|
name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'),
|
||||||
type: item.type,
|
type: item.type!,
|
||||||
url: item.url,
|
url: item.url,
|
||||||
substore: item.substore || false,
|
substore: item.substore || false,
|
||||||
interval: item.interval || 0,
|
interval: item.interval || 0,
|
||||||
@ -230,14 +230,22 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
|
|||||||
allowFixedInterval: item.allowFixedInterval || false,
|
allowFixedInterval: item.allowFixedInterval || false,
|
||||||
autoUpdate: item.autoUpdate ?? false,
|
autoUpdate: item.autoUpdate ?? false,
|
||||||
authToken: item.authToken,
|
authToken: item.authToken,
|
||||||
updated: new Date().getTime()
|
updated: new Date().getTime(),
|
||||||
} as IProfileItem
|
updateTimeout: item.updateTimeout || 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local
|
||||||
|
if (newItem.type === 'local') {
|
||||||
|
await setProfileStr(id, item.file || '')
|
||||||
|
return newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote
|
||||||
|
if (!item.url) throw new Error('Empty URL')
|
||||||
|
|
||||||
switch (newItem.type) {
|
|
||||||
case 'remote': {
|
|
||||||
const { userAgent, subscriptionTimeout = 30000 } = await getAppConfig()
|
const { userAgent, subscriptionTimeout = 30000 } = await getAppConfig()
|
||||||
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
||||||
if (!item.url) throw new Error('Empty URL')
|
const userItemTimeoutMs = (newItem.updateTimeout || 5) * 1000
|
||||||
|
|
||||||
const baseOptions: Omit<FetchOptions, 'useProxy' | 'timeout'> = {
|
const baseOptions: Omit<FetchOptions, 'useProxy' | 'timeout'> = {
|
||||||
url: item.url,
|
url: item.url,
|
||||||
@ -247,46 +255,25 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
|
|||||||
substore: newItem.substore || false
|
substore: newItem.substore || false
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: FetchResult
|
const fetchSub = (useProxy: boolean, timeout: number) =>
|
||||||
let finalUseProxy = newItem.useProxy
|
fetchAndValidateSubscription({ ...baseOptions, useProxy, timeout })
|
||||||
|
|
||||||
if (newItem.useProxy) {
|
let result: FetchResult
|
||||||
result = await fetchAndValidateSubscription({
|
if (newItem.useProxy || newItem.substore) {
|
||||||
...baseOptions,
|
result = await fetchSub(newItem.useProxy!, userItemTimeoutMs)
|
||||||
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 {
|
} else {
|
||||||
const smartTimeout = 5000
|
|
||||||
try {
|
try {
|
||||||
result = await fetchAndValidateSubscription({
|
result = await fetchSub(false, userItemTimeoutMs)
|
||||||
...baseOptions,
|
|
||||||
useProxy: false,
|
|
||||||
timeout: smartTimeout
|
|
||||||
})
|
|
||||||
} catch (directError) {
|
} catch (directError) {
|
||||||
try {
|
try {
|
||||||
result = await fetchAndValidateSubscription({
|
// smart fallback
|
||||||
...baseOptions,
|
result = await fetchSub(true, subscriptionTimeout)
|
||||||
useProxy: true,
|
|
||||||
timeout: smartTimeout
|
|
||||||
})
|
|
||||||
finalUseProxy = true
|
|
||||||
} catch {
|
} catch {
|
||||||
throw directError
|
throw directError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newItem.useProxy = finalUseProxy
|
|
||||||
const { data, headers } = result
|
const { data, headers } = result
|
||||||
|
|
||||||
if (headers['content-disposition'] && newItem.name === 'Remote File') {
|
if (headers['content-disposition'] && newItem.name === 'Remote File') {
|
||||||
@ -301,14 +288,8 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
|
|||||||
if (headers['subscription-userinfo']) {
|
if (headers['subscription-userinfo']) {
|
||||||
newItem.extra = parseSubinfo(headers['subscription-userinfo'])
|
newItem.extra = parseSubinfo(headers['subscription-userinfo'])
|
||||||
}
|
}
|
||||||
|
|
||||||
await setProfileStr(id, data)
|
await setProfileStr(id, data)
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'local': {
|
|
||||||
await setProfileStr(id, item.file || '')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newItem
|
return newItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,10 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
|||||||
const { item, updateProfileItem, onClose } = props
|
const { item, updateProfileItem, onClose } = props
|
||||||
const { overrideConfig } = useOverrideConfig()
|
const { overrideConfig } = useOverrideConfig()
|
||||||
const { items: overrideItems = [] } = overrideConfig || {}
|
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 inputWidth = 'w-[400px] md:w-[400px] lg:w-[600px] xl:w-[800px]'
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@ -40,6 +43,7 @@ const EditInfoModal: React.FC<Props> = (props) => {
|
|||||||
try {
|
try {
|
||||||
const updatedItem = {
|
const updatedItem = {
|
||||||
...values,
|
...values,
|
||||||
|
updateTimeout: values.updateTimeout ?? 5,
|
||||||
override: values.override?.filter(
|
override: values.override?.filter(
|
||||||
(i) =>
|
(i) =>
|
||||||
overrideItems.find((t) => t.id === i) && !overrideItems.find((t) => t.id === i)?.global
|
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')}>
|
<SettingItem title={t('profiles.editInfo.override.title')}>
|
||||||
<div>
|
<div>
|
||||||
{overrideItems
|
{overrideItems
|
||||||
|
|||||||
@ -484,6 +484,8 @@
|
|||||||
"profiles.editInfo.override.global": "Global",
|
"profiles.editInfo.override.global": "Global",
|
||||||
"profiles.editInfo.override.noAvailable": "No available overrides",
|
"profiles.editInfo.override.noAvailable": "No available overrides",
|
||||||
"profiles.editInfo.override.add": "Add Override",
|
"profiles.editInfo.override.add": "Add Override",
|
||||||
|
"profiles.editInfo.updateTimeout": "Update Timeout",
|
||||||
|
"profiles.editInfo.updateTimeoutPlaceholder": "Timeout in seconds",
|
||||||
"profiles.editFile.title": "Edit Profile",
|
"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.notice": "Note: Changes made here will be reset after profile update. For custom configurations, please use",
|
||||||
"profiles.editFile.override": "Override",
|
"profiles.editFile.override": "Override",
|
||||||
|
|||||||
@ -454,6 +454,8 @@
|
|||||||
"profiles.editInfo.override.global": "جهانی",
|
"profiles.editInfo.override.global": "جهانی",
|
||||||
"profiles.editInfo.override.noAvailable": "جایگزینیای در دسترس نیست",
|
"profiles.editInfo.override.noAvailable": "جایگزینیای در دسترس نیست",
|
||||||
"profiles.editInfo.override.add": "افزودن جایگزینی",
|
"profiles.editInfo.override.add": "افزودن جایگزینی",
|
||||||
|
"profiles.editInfo.updateTimeout": "زمان انتظار بهروزرسانی",
|
||||||
|
"profiles.editInfo.updateTimeoutPlaceholder": "زمان پایان دادن به ثانیه",
|
||||||
"profiles.editInfo.intervalPlaceholder": "مثال: 30 یا '0 * * * *'",
|
"profiles.editInfo.intervalPlaceholder": "مثال: 30 یا '0 * * * *'",
|
||||||
"profiles.editInfo.intervalInvalid": "نامعتبر",
|
"profiles.editInfo.intervalInvalid": "نامعتبر",
|
||||||
"profiles.editInfo.intervalMinutes": "فاصله زمانی ثابت به دقیقه",
|
"profiles.editInfo.intervalMinutes": "فاصله زمانی ثابت به دقیقه",
|
||||||
|
|||||||
@ -454,6 +454,8 @@
|
|||||||
"profiles.editInfo.override.global": "Глобальный",
|
"profiles.editInfo.override.global": "Глобальный",
|
||||||
"profiles.editInfo.override.noAvailable": "Нет доступных переопределений",
|
"profiles.editInfo.override.noAvailable": "Нет доступных переопределений",
|
||||||
"profiles.editInfo.override.add": "Добавить переопределение",
|
"profiles.editInfo.override.add": "Добавить переопределение",
|
||||||
|
"profiles.editInfo.updateTimeout": "Таймаут обновления",
|
||||||
|
"profiles.editInfo.updateTimeoutPlaceholder": "Время ожидания в секундах",
|
||||||
"profiles.editInfo.intervalPlaceholder": "например: 30 или '0 * * * *'",
|
"profiles.editInfo.intervalPlaceholder": "например: 30 или '0 * * * *'",
|
||||||
"profiles.editInfo.intervalInvalid": "Недействительно",
|
"profiles.editInfo.intervalInvalid": "Недействительно",
|
||||||
"profiles.editInfo.intervalMinutes": "Фиксированный интервал в минутах",
|
"profiles.editInfo.intervalMinutes": "Фиксированный интервал в минутах",
|
||||||
|
|||||||
@ -489,6 +489,8 @@
|
|||||||
"profiles.editInfo.override.global": "全局",
|
"profiles.editInfo.override.global": "全局",
|
||||||
"profiles.editInfo.override.noAvailable": "没有可用的覆写",
|
"profiles.editInfo.override.noAvailable": "没有可用的覆写",
|
||||||
"profiles.editInfo.override.add": "添加覆写",
|
"profiles.editInfo.override.add": "添加覆写",
|
||||||
|
"profiles.editInfo.updateTimeout": "更新超时时间",
|
||||||
|
"profiles.editInfo.updateTimeoutPlaceholder": "以秒为单位的超时时间",
|
||||||
"profiles.editFile.title": "编辑订阅",
|
"profiles.editFile.title": "编辑订阅",
|
||||||
"profiles.editFile.notice": "注意:此处编辑配置更新订阅后会还原,如需要自定义配置请使用",
|
"profiles.editFile.notice": "注意:此处编辑配置更新订阅后会还原,如需要自定义配置请使用",
|
||||||
"profiles.editFile.override": "覆写",
|
"profiles.editFile.override": "覆写",
|
||||||
|
|||||||
@ -489,6 +489,8 @@
|
|||||||
"profiles.editInfo.override.global": "全局",
|
"profiles.editInfo.override.global": "全局",
|
||||||
"profiles.editInfo.override.noAvailable": "沒有可用的覆寫",
|
"profiles.editInfo.override.noAvailable": "沒有可用的覆寫",
|
||||||
"profiles.editInfo.override.add": "添加覆寫",
|
"profiles.editInfo.override.add": "添加覆寫",
|
||||||
|
"profiles.editInfo.updateTimeout": "更新逾時時間",
|
||||||
|
"profiles.editInfo.updateTimeoutPlaceholder": "以秒為單位的超時時間",
|
||||||
"profiles.editFile.title": "編輯訂閱",
|
"profiles.editFile.title": "編輯訂閱",
|
||||||
"profiles.editFile.notice": "注意:此處編輯配置更新訂閱後會還原,如需要自定義配置請使用",
|
"profiles.editFile.notice": "注意:此處編輯配置更新訂閱後會還原,如需要自定義配置請使用",
|
||||||
"profiles.editFile.override": "覆寫",
|
"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
|
allowFixedInterval?: boolean
|
||||||
autoUpdate?: boolean
|
autoUpdate?: boolean
|
||||||
authToken?: string
|
authToken?: string
|
||||||
|
updateTimeout?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISubStoreSub {
|
interface ISubStoreSub {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user