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> {
|
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,85 +230,66 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
switch (newItem.type) {
|
// Local
|
||||||
case 'remote': {
|
if (newItem.type === 'local') {
|
||||||
const { userAgent, subscriptionTimeout = 30000 } = await getAppConfig()
|
await setProfileStr(id, item.file || '')
|
||||||
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
return newItem
|
||||||
if (!item.url) throw new Error('Empty URL')
|
}
|
||||||
|
|
||||||
const baseOptions: Omit<FetchOptions, 'useProxy' | 'timeout'> = {
|
// Remote
|
||||||
url: item.url,
|
if (!item.url) throw new Error('Empty URL')
|
||||||
mixedPort,
|
|
||||||
userAgent: userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`,
|
|
||||||
authToken: item.authToken,
|
|
||||||
substore: newItem.substore || false
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: FetchResult
|
const { userAgent, subscriptionTimeout = 30000 } = await getAppConfig()
|
||||||
let finalUseProxy = newItem.useProxy
|
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
||||||
|
const userItemTimeoutMs = (newItem.updateTimeout || 5) * 1000
|
||||||
|
|
||||||
if (newItem.useProxy) {
|
const baseOptions: Omit<FetchOptions, 'useProxy' | 'timeout'> = {
|
||||||
result = await fetchAndValidateSubscription({
|
url: item.url,
|
||||||
...baseOptions,
|
mixedPort,
|
||||||
useProxy: true,
|
userAgent: userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`,
|
||||||
timeout: subscriptionTimeout
|
authToken: item.authToken,
|
||||||
})
|
substore: newItem.substore || false
|
||||||
} 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newItem.useProxy = finalUseProxy
|
const fetchSub = (useProxy: boolean, timeout: number) =>
|
||||||
const { data, headers } = result
|
fetchAndValidateSubscription({ ...baseOptions, useProxy, timeout })
|
||||||
|
|
||||||
if (headers['content-disposition'] && newItem.name === 'Remote File') {
|
let result: FetchResult
|
||||||
newItem.name = parseFilename(headers['content-disposition'])
|
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
|
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