mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-19 12:59:09 +08:00
fix: 收紧远程配置拉取并强化更新安装确认(resolve #137)
This commit is contained in:
@@ -12,6 +12,26 @@ type AppWithQuitFlag = typeof app & { isQuiting?: boolean }
|
||||
// 通过类型扩展记录应用退出意图,避免使用 @ts-ignore。
|
||||
const appWithQuitFlag = app as AppWithQuitFlag
|
||||
|
||||
const REMOTE_CONFIG_ALLOWED_DOMAINS = ['chatlab.fun', '1app.top']
|
||||
const REMOTE_CONFIG_TIMEOUT_MS = 8000
|
||||
const REMOTE_CONFIG_MAX_BYTES = 1024 * 1024 // 1MB
|
||||
|
||||
function isAllowedRemoteConfigUrl(rawUrl: string): boolean {
|
||||
let parsed: URL
|
||||
try {
|
||||
parsed = new URL(rawUrl)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
|
||||
if (parsed.protocol !== 'https:') return false
|
||||
if (parsed.username || parsed.password) return false
|
||||
if (parsed.port && parsed.port !== '443') return false
|
||||
|
||||
const hostname = parsed.hostname.toLowerCase()
|
||||
return REMOTE_CONFIG_ALLOWED_DOMAINS.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`))
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册窗口和文件系统操作 IPC 处理器
|
||||
*/
|
||||
@@ -89,27 +109,55 @@ export function registerWindowHandlers(ctx: IpcContext): void {
|
||||
|
||||
// 获取远程配置(支持 JSON 和纯文本/Markdown)
|
||||
ipcMain.handle('app:fetchRemoteConfig', async (_, url: string) => {
|
||||
const normalizedUrl = typeof url === 'string' ? url.trim() : ''
|
||||
if (!isAllowedRemoteConfigUrl(normalizedUrl)) {
|
||||
return { success: false, error: 'URL is not allowed' }
|
||||
}
|
||||
|
||||
const abortController = new AbortController()
|
||||
const timeout = setTimeout(() => abortController.abort(), REMOTE_CONFIG_TIMEOUT_MS)
|
||||
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
const response = await fetch(normalizedUrl, { signal: abortController.signal })
|
||||
const finalUrl = response.url || normalizedUrl
|
||||
if (!isAllowedRemoteConfigUrl(finalUrl)) {
|
||||
return { success: false, error: 'Redirect URL is not allowed' }
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || ''
|
||||
const contentLength = Number(response.headers.get('content-length') || 0)
|
||||
|
||||
if (Number.isFinite(contentLength) && contentLength > REMOTE_CONFIG_MAX_BYTES) {
|
||||
return { success: false, error: 'Response is too large' }
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
return { success: false, error: `HTTP ${response.status}: ${response.statusText}` }
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(await response.arrayBuffer())
|
||||
if (buffer.length > REMOTE_CONFIG_MAX_BYTES) {
|
||||
return { success: false, error: 'Response is too large' }
|
||||
}
|
||||
|
||||
// 根据 Content-Type 或 URL 后缀决定解析方式
|
||||
const isJson = contentType.includes('application/json') || url.endsWith('.json')
|
||||
const isJson = contentType.includes('application/json') || finalUrl.endsWith('.json')
|
||||
|
||||
if (isJson) {
|
||||
const data = await response.json()
|
||||
const data = JSON.parse(buffer.toString('utf-8'))
|
||||
return { success: true, data }
|
||||
} else {
|
||||
// 纯文本/Markdown 等其他格式
|
||||
const data = await response.text()
|
||||
const data = buffer.toString('utf-8')
|
||||
return { success: true, data }
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
return { success: false, error: 'Request timeout' }
|
||||
}
|
||||
return { success: false, error: String(error) }
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const R2_MIRROR_URL = 'https://chatlab.1app.top/releases/download'
|
||||
// 更新源类型
|
||||
type UpdateSource = 'github' | 'r2'
|
||||
|
||||
// 当前使用的更新源(默认 R2 优先)
|
||||
// 当前使用的更新源(默认 R2 优先,GitHub 作为网络失败兜底)
|
||||
let currentSource: UpdateSource = 'r2'
|
||||
|
||||
// 是否已尝试过备用源
|
||||
@@ -57,7 +57,6 @@ function switchToR2Mirror(): void {
|
||||
*/
|
||||
function switchToGitHub(): void {
|
||||
currentSource = 'github'
|
||||
// 使用 GitHub 作为 generic provider
|
||||
autoUpdater.setFeedURL({
|
||||
provider: 'github',
|
||||
owner: 'hellodigua',
|
||||
@@ -117,7 +116,7 @@ const checkUpdate = (win) => {
|
||||
configureUpdateProxy()
|
||||
|
||||
autoUpdater.autoDownload = false // 自动下载
|
||||
autoUpdater.autoInstallOnAppQuit = true // 应用退出后自动安装
|
||||
autoUpdater.autoInstallOnAppQuit = false // 关闭退出自动安装,必须显式确认安装
|
||||
|
||||
// 开发模式下模拟更新检测(需要创建 dev-app-update.yml 文件)
|
||||
// 取消下面的注释来启用开发模式更新测试
|
||||
@@ -187,9 +186,9 @@ const checkUpdate = (win) => {
|
||||
.showMessageBox({
|
||||
title: t('update.downloadComplete'),
|
||||
message: t('update.readyToInstall'),
|
||||
buttons: [t('update.install'), platform.isMacOS ? t('update.remindLater') : t('update.installOnQuit')],
|
||||
buttons: [t('update.install'), t('update.remindLater')],
|
||||
defaultId: 1,
|
||||
cancelId: 2,
|
||||
cancelId: 1,
|
||||
type: 'question',
|
||||
})
|
||||
.then(async (result) => {
|
||||
@@ -230,11 +229,11 @@ const checkUpdate = (win) => {
|
||||
}
|
||||
})
|
||||
|
||||
// 错误处理(智能切换备用源)
|
||||
// 错误处理(网络失败时切换备用源)
|
||||
autoUpdater.on('error', (err) => {
|
||||
logger.error(`[Update] Update error (${currentSource}): ${err.message || err}`)
|
||||
|
||||
// 如果是 R2 源且为网络错误,尝试切换到 GitHub 备用源
|
||||
// 默认 R2 源网络失败时,尝试切换到 GitHub
|
||||
if (currentSource === 'r2' && !hasTriedFallback && isNetworkError(err)) {
|
||||
hasTriedFallback = true
|
||||
logger.info('[Update] R2 mirror failed, trying GitHub fallback...')
|
||||
|
||||
Reference in New Issue
Block a user