mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 05:11:24 +08:00
* feat: add update channel functionality for beta testing - Introduced a new IPC channel for setting the update channel. - Implemented logic in AppUpdater to handle update channel changes. - Updated settings to include a beta testing toggle, allowing users to switch between stable and beta update channels. - Enhanced the settings UI to reflect the new beta testing option. * add i18n * update i18n * update i18n * refactor: rename update channel to feed URL and update related functionality - Changed IPC channel from App_SetUpdateChannel to App_SetFeedUrl. - Updated AppUpdater to set feed URL instead of update channel. - Modified preload and settings to reflect the new feed URL functionality. - Added constants for production and early access feed URLs. * refactor: remove setAutoUpdate method from API - Eliminated the setAutoUpdate method from the API object in preload index, streamlining the IPC communication interface. * refactor: update early access feed URL and improve tooltip descriptions - Changed EARLY_ACCESS_FEED_URL to point to the latest GitHub release. - Simplified the setEarlyAccess function to directly set the feed URL. - Added tooltips for early access settings in multiple languages to inform users about potential instability and the need for data backup. * feat(migrate): add early access setting to state configuration - Introduced a new state setting 'earlyAccess' and initialized it to false in the migration configuration. * fix(i18n): update early access tooltip translations for clarity - Revised the tooltip descriptions for the early access feature in English, Simplified Chinese, and Traditional Chinese to enhance clarity and ensure consistency in messaging regarding potential instability and the importance of data backup. * feat: introduce FeedUrl enum for centralized feed URL management - Added a new enum `FeedUrl` in the constants file to define production and early access feed URLs. - Updated relevant IPC handlers and services to utilize the `FeedUrl` enum for type safety and consistency. - Refactored the configuration manager to include methods for getting and setting the feed URL using the new enum. * feat(settings): initialize early access and auto-update settings in AboutSettings component - Added initialization for early access and auto-check update settings in the AboutSettings component to enhance user configuration options. --------- Co-authored-by: beyondkmp <beyondkmkp@gmail.com>
151 lines
4.5 KiB
TypeScript
151 lines
4.5 KiB
TypeScript
import { isWin } from '@main/constant'
|
||
import { locales } from '@main/utils/locales'
|
||
import { IpcChannel } from '@shared/IpcChannel'
|
||
import { FeedUrl } from '@shared/config/constant'
|
||
import { UpdateInfo } from 'builder-util-runtime'
|
||
import { app, BrowserWindow, dialog } from 'electron'
|
||
import logger from 'electron-log'
|
||
import { AppUpdater as _AppUpdater, autoUpdater } from 'electron-updater'
|
||
|
||
import icon from '../../../build/icon.png?asset'
|
||
import { configManager } from './ConfigManager'
|
||
|
||
export default class AppUpdater {
|
||
autoUpdater: _AppUpdater = autoUpdater
|
||
private releaseInfo: UpdateInfo | undefined
|
||
|
||
constructor(mainWindow: BrowserWindow) {
|
||
logger.transports.file.level = 'info'
|
||
|
||
autoUpdater.logger = logger
|
||
autoUpdater.forceDevUpdateConfig = !app.isPackaged
|
||
autoUpdater.autoDownload = configManager.getAutoUpdate()
|
||
autoUpdater.autoInstallOnAppQuit = configManager.getAutoUpdate()
|
||
autoUpdater.setFeedURL(configManager.getFeedUrl())
|
||
|
||
// 检测下载错误
|
||
autoUpdater.on('error', (error) => {
|
||
// 简单记录错误信息和时间戳
|
||
logger.error('更新异常', {
|
||
message: error.message,
|
||
stack: error.stack,
|
||
time: new Date().toISOString()
|
||
})
|
||
mainWindow.webContents.send(IpcChannel.UpdateError, error)
|
||
})
|
||
|
||
autoUpdater.on('update-available', (releaseInfo: UpdateInfo) => {
|
||
logger.info('检测到新版本', releaseInfo)
|
||
mainWindow.webContents.send(IpcChannel.UpdateAvailable, releaseInfo)
|
||
})
|
||
|
||
// 检测到不需要更新时
|
||
autoUpdater.on('update-not-available', () => {
|
||
mainWindow.webContents.send(IpcChannel.UpdateNotAvailable)
|
||
})
|
||
|
||
// 更新下载进度
|
||
autoUpdater.on('download-progress', (progress) => {
|
||
mainWindow.webContents.send(IpcChannel.DownloadProgress, progress)
|
||
})
|
||
|
||
// 当需要更新的内容下载完成后
|
||
autoUpdater.on('update-downloaded', (releaseInfo: UpdateInfo) => {
|
||
mainWindow.webContents.send(IpcChannel.UpdateDownloaded, releaseInfo)
|
||
this.releaseInfo = releaseInfo
|
||
logger.info('下载完成', releaseInfo)
|
||
})
|
||
|
||
this.autoUpdater = autoUpdater
|
||
}
|
||
|
||
public setAutoUpdate(isActive: boolean) {
|
||
autoUpdater.autoDownload = isActive
|
||
autoUpdater.autoInstallOnAppQuit = isActive
|
||
}
|
||
|
||
public setFeedUrl(feedUrl: FeedUrl) {
|
||
autoUpdater.setFeedURL(feedUrl)
|
||
configManager.setFeedUrl(feedUrl)
|
||
}
|
||
|
||
public async checkForUpdates() {
|
||
if (isWin && 'PORTABLE_EXECUTABLE_DIR' in process.env) {
|
||
return {
|
||
currentVersion: app.getVersion(),
|
||
updateInfo: null
|
||
}
|
||
}
|
||
|
||
try {
|
||
const update = await this.autoUpdater.checkForUpdates()
|
||
if (update?.isUpdateAvailable && !this.autoUpdater.autoDownload) {
|
||
// 如果 autoDownload 为 false,则需要再调用下面的函数触发下
|
||
// do not use await, because it will block the return of this function
|
||
this.autoUpdater.downloadUpdate()
|
||
}
|
||
|
||
return {
|
||
currentVersion: this.autoUpdater.currentVersion,
|
||
updateInfo: update?.updateInfo
|
||
}
|
||
} catch (error) {
|
||
logger.error('Failed to check for update:', error)
|
||
return {
|
||
currentVersion: app.getVersion(),
|
||
updateInfo: null
|
||
}
|
||
}
|
||
}
|
||
|
||
public async showUpdateDialog(mainWindow: BrowserWindow) {
|
||
if (!this.releaseInfo) {
|
||
return
|
||
}
|
||
const locale = locales[configManager.getLanguage()]
|
||
const { update: updateLocale } = locale.translation
|
||
|
||
let detail = this.formatReleaseNotes(this.releaseInfo.releaseNotes)
|
||
if (detail === '') {
|
||
detail = updateLocale.noReleaseNotes
|
||
}
|
||
|
||
dialog
|
||
.showMessageBox({
|
||
type: 'info',
|
||
title: updateLocale.title,
|
||
icon,
|
||
message: updateLocale.message.replace('{{version}}', this.releaseInfo.version),
|
||
detail,
|
||
buttons: [updateLocale.later, updateLocale.install],
|
||
defaultId: 1,
|
||
cancelId: 0
|
||
})
|
||
.then(({ response }) => {
|
||
if (response === 1) {
|
||
app.isQuitting = true
|
||
setImmediate(() => autoUpdater.quitAndInstall())
|
||
} else {
|
||
mainWindow.webContents.send(IpcChannel.UpdateDownloadedCancelled)
|
||
}
|
||
})
|
||
}
|
||
|
||
private formatReleaseNotes(releaseNotes: string | ReleaseNoteInfo[] | null | undefined): string {
|
||
if (!releaseNotes) {
|
||
return ''
|
||
}
|
||
|
||
if (typeof releaseNotes === 'string') {
|
||
return releaseNotes
|
||
}
|
||
|
||
return releaseNotes.map((note) => note.note).join('\n')
|
||
}
|
||
}
|
||
|
||
interface ReleaseNoteInfo {
|
||
readonly version: string
|
||
readonly note: string | null
|
||
}
|