mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-04-13 08:00:30 +08:00
feat: add GitHub proxy selection with auto multi-source fallback
This commit is contained in:
parent
0f1cd352db
commit
06591b50f4
@ -10,25 +10,47 @@ import i18next from 'i18next'
|
|||||||
import { mainWindow } from '../window'
|
import { mainWindow } from '../window'
|
||||||
import { appLogger } from '../utils/logger'
|
import { appLogger } from '../utils/logger'
|
||||||
import { dataDir, exeDir, exePath, isPortable, resourcesFilesDir } from '../utils/dirs'
|
import { dataDir, exeDir, exePath, isPortable, resourcesFilesDir } from '../utils/dirs'
|
||||||
import { getControledMihomoConfig } from '../config'
|
import { getAppConfig, getControledMihomoConfig } from '../config'
|
||||||
import { checkAdminPrivileges } from '../core/manager'
|
import { checkAdminPrivileges } from '../core/manager'
|
||||||
import { parse } from '../utils/yaml'
|
import { parse } from '../utils/yaml'
|
||||||
import * as chromeRequest from '../utils/chromeRequest'
|
import * as chromeRequest from '../utils/chromeRequest'
|
||||||
|
|
||||||
export async function checkUpdate(): Promise<IAppVersion | undefined> {
|
const GITHUB_PROXIES = ['https://gh-proxy.org', 'https://ghfast.top', 'https://down.clashparty.org']
|
||||||
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
|
||||||
const res = await chromeRequest.get(
|
function buildDownloadUrls(githubUrl: string, proxyPref = ''): string[] {
|
||||||
'https://github.com/mihomo-party-org/mihomo-party/releases/latest/download/latest.yml',
|
if (proxyPref === 'direct') return [githubUrl]
|
||||||
{
|
if (proxyPref && proxyPref !== 'auto') return [`${proxyPref}/${githubUrl}`]
|
||||||
headers: { 'Content-Type': 'application/octet-stream' },
|
// auto: try each proxy then fall back to direct
|
||||||
proxy: {
|
return [...GITHUB_PROXIES.map((p) => `${p}/${githubUrl}`), githubUrl]
|
||||||
protocol: 'http',
|
}
|
||||||
host: '127.0.0.1',
|
|
||||||
port: mixedPort
|
async function tryDownload(
|
||||||
},
|
urls: string[],
|
||||||
responseType: 'text'
|
options: Parameters<typeof chromeRequest.get>[1]
|
||||||
|
): Promise<Awaited<ReturnType<typeof chromeRequest.get>>> {
|
||||||
|
let lastError: unknown
|
||||||
|
for (const url of urls) {
|
||||||
|
try {
|
||||||
|
return await chromeRequest.get(url, options)
|
||||||
|
} catch (e) {
|
||||||
|
lastError = e
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
throw lastError
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkUpdate(): Promise<IAppVersion | undefined> {
|
||||||
|
const [{ 'mixed-port': mixedPort = 7890 }, { githubProxy = '' }] = await Promise.all([
|
||||||
|
getControledMihomoConfig(),
|
||||||
|
getAppConfig()
|
||||||
|
])
|
||||||
|
const githubUrl =
|
||||||
|
'https://github.com/mihomo-party-org/mihomo-party/releases/latest/download/latest.yml'
|
||||||
|
const res = await tryDownload(buildDownloadUrls(githubUrl, githubProxy), {
|
||||||
|
headers: { 'Content-Type': 'application/octet-stream' },
|
||||||
|
proxy: { protocol: 'http', host: '127.0.0.1', port: mixedPort },
|
||||||
|
responseType: 'text'
|
||||||
|
})
|
||||||
const latest = parse(res.data as string) as IAppVersion
|
const latest = parse(res.data as string) as IAppVersion
|
||||||
const currentVersion = app.getVersion()
|
const currentVersion = app.getVersion()
|
||||||
if (compareVersions(latest.version, currentVersion) > 0) {
|
if (compareVersions(latest.version, currentVersion) > 0) {
|
||||||
@ -57,8 +79,11 @@ function compareVersions(a: string, b: string): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadAndInstallUpdate(version: string): Promise<void> {
|
export async function downloadAndInstallUpdate(version: string): Promise<void> {
|
||||||
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
const [{ 'mixed-port': mixedPort = 7890 }, { githubProxy = '' }] = await Promise.all([
|
||||||
const baseUrl = `https://github.com/mihomo-party-org/mihomo-party/releases/download/v${version}/`
|
getControledMihomoConfig(),
|
||||||
|
getAppConfig()
|
||||||
|
])
|
||||||
|
const githubBase = `https://github.com/mihomo-party-org/mihomo-party/releases/download/v${version}/`
|
||||||
const fileMap = {
|
const fileMap = {
|
||||||
'win32-x64': `clash-party-windows-${version}-x64-setup.exe`,
|
'win32-x64': `clash-party-windows-${version}-x64-setup.exe`,
|
||||||
'win32-ia32': `clash-party-windows-${version}-ia32-setup.exe`,
|
'win32-ia32': `clash-party-windows-${version}-ia32-setup.exe`,
|
||||||
@ -84,34 +109,27 @@ export async function downloadAndInstallUpdate(version: string): Promise<void> {
|
|||||||
file = file.replace('macos', 'catalina')
|
file = file.replace('macos', 'catalina')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const proxy = { protocol: 'http' as const, host: '127.0.0.1', port: mixedPort }
|
||||||
try {
|
try {
|
||||||
if (!existsSync(path.join(dataDir(), file))) {
|
if (!existsSync(path.join(dataDir(), file))) {
|
||||||
const sha256Res = await chromeRequest.get(`${baseUrl}${file}.sha256`, {
|
const sha256Res = await tryDownload(
|
||||||
proxy: {
|
buildDownloadUrls(`${githubBase}${file}.sha256`, githubProxy),
|
||||||
protocol: 'http',
|
{ proxy, responseType: 'text' }
|
||||||
host: '127.0.0.1',
|
)
|
||||||
port: mixedPort
|
|
||||||
},
|
|
||||||
responseType: 'text'
|
|
||||||
})
|
|
||||||
const expectedHash = (sha256Res.data as string).trim().split(/\s+/)[0]
|
const expectedHash = (sha256Res.data as string).trim().split(/\s+/)[0]
|
||||||
const res = await chromeRequest.get(`${baseUrl}${file}`, {
|
const res = await tryDownload(
|
||||||
responseType: 'arraybuffer',
|
buildDownloadUrls(`${githubBase}${file}`, githubProxy),
|
||||||
timeout: 0,
|
{
|
||||||
proxy: {
|
responseType: 'arraybuffer',
|
||||||
protocol: 'http',
|
timeout: 0,
|
||||||
host: '127.0.0.1',
|
proxy,
|
||||||
port: mixedPort
|
headers: { 'Content-Type': 'application/octet-stream' },
|
||||||
},
|
onProgress: (loaded: number, total: number) => {
|
||||||
headers: {
|
mainWindow?.webContents.send('updateDownloadProgress', {
|
||||||
'Content-Type': 'application/octet-stream'
|
status: 'downloading',
|
||||||
},
|
percent: Math.round((loaded / total) * 100)
|
||||||
onProgress: (loaded, total) => {
|
})
|
||||||
mainWindow?.webContents.send('updateDownloadProgress', {
|
}
|
||||||
status: 'downloading',
|
|
||||||
percent: Math.round((loaded / total) * 100)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
mainWindow?.webContents.send('updateDownloadProgress', { status: 'verifying' })
|
mainWindow?.webContents.send('updateDownloadProgress', { status: 'verifying' })
|
||||||
const fileBuffer = Buffer.from(res.data as ArrayBuffer)
|
const fileBuffer = Buffer.from(res.data as ArrayBuffer)
|
||||||
|
|||||||
@ -9,9 +9,18 @@ import { stopCore } from '../core/manager'
|
|||||||
import { mihomoCoreDir } from './dirs'
|
import { mihomoCoreDir } from './dirs'
|
||||||
import * as chromeRequest from './chromeRequest'
|
import * as chromeRequest from './chromeRequest'
|
||||||
import { createLogger } from './logger'
|
import { createLogger } from './logger'
|
||||||
|
import { getAppConfig } from '../config'
|
||||||
|
|
||||||
const log = createLogger('GitHub')
|
const log = createLogger('GitHub')
|
||||||
|
|
||||||
|
const GITHUB_PROXIES = ['https://gh-proxy.org', 'https://ghfast.top', 'https://down.clashparty.org']
|
||||||
|
|
||||||
|
function buildDownloadUrls(githubUrl: string, proxyPref = ''): string[] {
|
||||||
|
if (proxyPref === 'direct') return [githubUrl]
|
||||||
|
if (proxyPref && proxyPref !== 'auto') return [`${proxyPref}/${githubUrl}`]
|
||||||
|
return [...GITHUB_PROXIES.map((p) => `${p}/${githubUrl}`), githubUrl]
|
||||||
|
}
|
||||||
|
|
||||||
export interface GitHubTag {
|
export interface GitHubTag {
|
||||||
name: string
|
name: string
|
||||||
zipball_url: string
|
zipball_url: string
|
||||||
@ -115,22 +124,28 @@ export function clearVersionCache(owner: string, repo: string): void {
|
|||||||
* @param outputPath 输出路径
|
* @param outputPath 输出路径
|
||||||
*/
|
*/
|
||||||
async function downloadGitHubAsset(url: string, outputPath: string): Promise<void> {
|
async function downloadGitHubAsset(url: string, outputPath: string): Promise<void> {
|
||||||
try {
|
const { githubProxy = '' } = await getAppConfig()
|
||||||
log.debug(`Downloading asset from ${url}`)
|
const urls = buildDownloadUrls(url, githubProxy)
|
||||||
const response = await chromeRequest.get(url, {
|
let lastError: unknown
|
||||||
responseType: 'arraybuffer',
|
for (const candidate of urls) {
|
||||||
timeout: 30000
|
try {
|
||||||
})
|
log.debug(`Downloading asset from ${candidate}`)
|
||||||
|
const response = await chromeRequest.get(candidate, {
|
||||||
await writeFile(outputPath, Buffer.from(response.data as Buffer))
|
responseType: 'arraybuffer',
|
||||||
log.debug(`Successfully downloaded asset to ${outputPath}`)
|
timeout: 30000
|
||||||
} catch (error) {
|
})
|
||||||
log.error(`Failed to download asset from ${url}`, error)
|
await writeFile(outputPath, Buffer.from(response.data as Buffer))
|
||||||
if (error instanceof Error) {
|
log.debug(`Successfully downloaded asset to ${outputPath}`)
|
||||||
throw new Error(`Download error: ${error.message}`)
|
return
|
||||||
|
} catch (error) {
|
||||||
|
log.warn(`Download failed from ${candidate}, trying next`, error)
|
||||||
|
lastError = error
|
||||||
}
|
}
|
||||||
throw new Error('Failed to download core file')
|
|
||||||
}
|
}
|
||||||
|
log.error(`Failed to download asset from all sources`, lastError)
|
||||||
|
throw lastError instanceof Error
|
||||||
|
? new Error(`Download error: ${lastError.message}`)
|
||||||
|
: new Error('Failed to download core file')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -66,6 +66,7 @@ const GeneralConfig: React.FC = () => {
|
|||||||
customTheme = 'default.css',
|
customTheme = 'default.css',
|
||||||
envType = [platform === 'win32' ? 'powershell' : 'bash'],
|
envType = [platform === 'win32' ? 'powershell' : 'bash'],
|
||||||
autoCheckUpdate,
|
autoCheckUpdate,
|
||||||
|
githubProxy = 'auto',
|
||||||
appTheme = 'system',
|
appTheme = 'system',
|
||||||
language = 'zh-CN',
|
language = 'zh-CN',
|
||||||
triggerMainWindowBehavior = 'show',
|
triggerMainWindowBehavior = 'show',
|
||||||
@ -171,6 +172,24 @@ const GeneralConfig: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
<SettingItem title={t('settings.githubProxy')} divider>
|
||||||
|
<Select
|
||||||
|
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
|
||||||
|
className="w-50"
|
||||||
|
size="sm"
|
||||||
|
selectedKeys={[githubProxy]}
|
||||||
|
aria-label={t('settings.githubProxy')}
|
||||||
|
onSelectionChange={(v) => {
|
||||||
|
patchAppConfig({ githubProxy: Array.from(v)[0] as string })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectItem key="auto">{t('settings.githubProxy.auto')}</SelectItem>
|
||||||
|
<SelectItem key="direct">{t('settings.githubProxy.direct')}</SelectItem>
|
||||||
|
<SelectItem key="https://gh-proxy.org">gh-proxy.org</SelectItem>
|
||||||
|
<SelectItem key="https://ghfast.top">ghfast.top</SelectItem>
|
||||||
|
<SelectItem key="https://down.clashparty.org">down.clashparty.org</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</SettingItem>
|
||||||
<SettingItem title={t('settings.silentStart')} divider>
|
<SettingItem title={t('settings.silentStart')} divider>
|
||||||
<Switch
|
<Switch
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -204,7 +223,7 @@ const GeneralConfig: React.FC = () => {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Input
|
<Input
|
||||||
size="sm"
|
size="sm"
|
||||||
className="w-[100px]"
|
className="w-25"
|
||||||
type="number"
|
type="number"
|
||||||
value={autoQuitWithoutCoreDelay.toString()}
|
value={autoQuitWithoutCoreDelay.toString()}
|
||||||
onValueChange={async (v: string) => {
|
onValueChange={async (v: string) => {
|
||||||
@ -240,7 +259,7 @@ const GeneralConfig: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
|
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
|
||||||
className="w-[150px]"
|
className="w-37.5"
|
||||||
size="sm"
|
size="sm"
|
||||||
selectionMode="multiple"
|
selectionMode="multiple"
|
||||||
selectedKeys={new Set(envType)}
|
selectedKeys={new Set(envType)}
|
||||||
|
|||||||
@ -707,5 +707,9 @@
|
|||||||
"network.topology.sourcePort": "Source Port",
|
"network.topology.sourcePort": "Source Port",
|
||||||
"network.topology.waiting": "Waiting for connections...",
|
"network.topology.waiting": "Waiting for connections...",
|
||||||
"network.topology.pause": "Pause",
|
"network.topology.pause": "Pause",
|
||||||
"network.topology.resume": "Resume"
|
"network.topology.resume": "Resume",
|
||||||
|
"guide.end.description": "Now that you understand the basic usage of the software, import your subscription and start using it. Enjoy!\nYou can also join our official <a href=\"https://t.me/mihomo_party_group\" target=\"_blank\">Telegram group</a> for the latest news.",
|
||||||
|
"settings.githubProxy": "GitHub Download Proxy",
|
||||||
|
"settings.githubProxy.auto": "Auto (proxy first)",
|
||||||
|
"settings.githubProxy.direct": "Direct"
|
||||||
}
|
}
|
||||||
@ -671,5 +671,8 @@
|
|||||||
"network.topology.sourcePort": "پورت منبع",
|
"network.topology.sourcePort": "پورت منبع",
|
||||||
"network.topology.waiting": "در انتظار اتصال...",
|
"network.topology.waiting": "در انتظار اتصال...",
|
||||||
"network.topology.pause": "مکث",
|
"network.topology.pause": "مکث",
|
||||||
"network.topology.resume": "ادامه"
|
"network.topology.resume": "ادامه",
|
||||||
|
"settings.githubProxy": "پروکسی دانلود GitHub",
|
||||||
|
"settings.githubProxy.auto": "خودکار (پروکسی اول)",
|
||||||
|
"settings.githubProxy.direct": "مستقیم"
|
||||||
}
|
}
|
||||||
@ -673,5 +673,8 @@
|
|||||||
"network.topology.sourcePort": "Порт источника",
|
"network.topology.sourcePort": "Порт источника",
|
||||||
"network.topology.waiting": "Ожидание подключений...",
|
"network.topology.waiting": "Ожидание подключений...",
|
||||||
"network.topology.pause": "Пауза",
|
"network.topology.pause": "Пауза",
|
||||||
"network.topology.resume": "Возобновить"
|
"network.topology.resume": "Возобновить",
|
||||||
|
"settings.githubProxy": "Прокси для загрузки GitHub",
|
||||||
|
"settings.githubProxy.auto": "Авто (сначала прокси)",
|
||||||
|
"settings.githubProxy.direct": "Прямое подключение"
|
||||||
}
|
}
|
||||||
@ -707,5 +707,8 @@
|
|||||||
"network.topology.sourcePort": "来源端口",
|
"network.topology.sourcePort": "来源端口",
|
||||||
"network.topology.waiting": "等待连接数据...",
|
"network.topology.waiting": "等待连接数据...",
|
||||||
"network.topology.pause": "暂停",
|
"network.topology.pause": "暂停",
|
||||||
"network.topology.resume": "恢复"
|
"network.topology.resume": "恢复",
|
||||||
|
"settings.githubProxy": "GitHub 下载代理",
|
||||||
|
"settings.githubProxy.auto": "自动(优先代理)",
|
||||||
|
"settings.githubProxy.direct": "直连"
|
||||||
}
|
}
|
||||||
@ -707,5 +707,8 @@
|
|||||||
"network.topology.sourcePort": "來源連接埠",
|
"network.topology.sourcePort": "來源連接埠",
|
||||||
"network.topology.waiting": "等待連線資料...",
|
"network.topology.waiting": "等待連線資料...",
|
||||||
"network.topology.pause": "暫停",
|
"network.topology.pause": "暫停",
|
||||||
"network.topology.resume": "繼續"
|
"network.topology.resume": "繼續",
|
||||||
|
"settings.githubProxy": "GitHub 下載代理",
|
||||||
|
"settings.githubProxy.auto": "自動(優先代理)",
|
||||||
|
"settings.githubProxy.direct": "直連"
|
||||||
}
|
}
|
||||||
1
src/shared/types.d.ts
vendored
1
src/shared/types.d.ts
vendored
@ -303,6 +303,7 @@ interface IAppConfig {
|
|||||||
appTheme: AppTheme
|
appTheme: AppTheme
|
||||||
customTheme?: string
|
customTheme?: string
|
||||||
autoCheckUpdate: boolean
|
autoCheckUpdate: boolean
|
||||||
|
githubProxy?: string
|
||||||
silentStart: boolean
|
silentStart: boolean
|
||||||
autoCloseConnection: boolean
|
autoCloseConnection: boolean
|
||||||
sysProxy: ISysProxyConfig
|
sysProxy: ISysProxyConfig
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user