mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 14:31:35 +08:00
* Revert "feat: optimize minapp cache with LRU (#8160)"
This reverts commit f0043b4be5.
* feat: integrate logger service and enhance logging throughout the application
- Added a new LoggerService to standardize logging across the application.
- Replaced console.error and console.warn calls with logger methods for improved consistency and error tracking.
- Introduced a new IPC channel for logging messages to the main process.
- Updated various components and services to utilize the new logging system, enhancing error handling and debugging capabilities.
* refactor: enhance logging and error handling across various components
- Integrated the LoggerService for consistent logging throughout the application.
- Updated multiple components and services to utilize the new logging system, improving error tracking and debugging capabilities.
- Refactored file handling and error management in several services to enhance reliability and clarity.
- Improved the structure and readability of the codebase by removing redundant checks and simplifying logic.
* chore: update TypeScript configuration and enhance test setup
- Added test mock paths to tsconfig.web.json for improved test coverage.
- Configured Vitest to include a setup file for main tests, ensuring consistent test environment.
- Updated IPC logger context for better clarity in logging.
- Enhanced LoggerService to handle undefined values gracefully.
- Mocked LoggerService globally in renderer tests to streamline testing process.
* refactor: standardize logging across ProxyManager and ReduxService
- Replaced instances of Logger with logger for consistent logging implementation.
- Improved logging clarity in ProxyManager's configureProxy method and ReduxService's state handling.
- Enhanced error logging in ReduxService to align with the new logging system.
* refactor: reorganize LoggerService for improved clarity and consistency
- Moved the definition of SYSTEM_INFO, APP_VERSION, and DEFAULT_LEVEL to enhance code organization.
- Simplified the getIsDev function in the renderer LoggerService for better readability.
- Updated logging conditions to ensure messages are logged correctly based on context.
* docs: add usage instructions for LoggerService and clean up logging code
- Included important usage instructions for LoggerService in both English and Chinese.
- Commented out the console transport in LoggerService to streamline logging.
- Improved logging message formatting in MCPService for clarity.
- Removed redundant logging statements in SelectionService to enhance code cleanliness.
* refactor: update LoggerService documentation paths and enhance logging implementation
- Changed the documentation paths for LoggerService usage instructions to `docs/technical/how-to-use-logger-en.md` and `docs/technical/how-to-use-logger-zh.md`.
- Replaced console logging with the loggerService in various components, including `MCPSettings`, `BlockManager`, and multiple callback files, to ensure consistent logging practices across the application.
- Improved the clarity and context of log messages for better debugging and monitoring.
* docs: emphasize logger usage guidelines in documentation
- Added a note in both English and Chinese documentation to discourage the use of `console.xxx` for logging unless necessary, promoting consistent logging practices across the application.
290 lines
9.1 KiB
TypeScript
290 lines
9.1 KiB
TypeScript
import { loggerService } from '@logger'
|
||
import { isWin } from '@main/constant'
|
||
import { locales } from '@main/utils/locales'
|
||
import { generateUserAgent } from '@main/utils/systemInfo'
|
||
import { FeedUrl, UpgradeChannel } from '@shared/config/constant'
|
||
import { IpcChannel } from '@shared/IpcChannel'
|
||
import { CancellationToken, UpdateInfo } from 'builder-util-runtime'
|
||
import { app, BrowserWindow, dialog } from 'electron'
|
||
import { AppUpdater as _AppUpdater, autoUpdater, NsisUpdater, UpdateCheckResult } from 'electron-updater'
|
||
import path from 'path'
|
||
|
||
import icon from '../../../build/icon.png?asset'
|
||
import { configManager } from './ConfigManager'
|
||
|
||
const logger = loggerService.withContext('AppUpdater')
|
||
|
||
export default class AppUpdater {
|
||
autoUpdater: _AppUpdater = autoUpdater
|
||
private releaseInfo: UpdateInfo | undefined
|
||
private cancellationToken: CancellationToken = new CancellationToken()
|
||
private updateCheckResult: UpdateCheckResult | null = null
|
||
|
||
constructor(mainWindow: BrowserWindow) {
|
||
autoUpdater.logger = logger
|
||
autoUpdater.forceDevUpdateConfig = !app.isPackaged
|
||
autoUpdater.autoDownload = configManager.getAutoUpdate()
|
||
autoUpdater.autoInstallOnAppQuit = configManager.getAutoUpdate()
|
||
autoUpdater.requestHeaders = {
|
||
...autoUpdater.requestHeaders,
|
||
'User-Agent': generateUserAgent()
|
||
}
|
||
|
||
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)
|
||
})
|
||
|
||
if (isWin) {
|
||
;(autoUpdater as NsisUpdater).installDirectory = path.dirname(app.getPath('exe'))
|
||
}
|
||
|
||
this.autoUpdater = autoUpdater
|
||
}
|
||
|
||
private async _getPreReleaseVersionFromGithub(channel: UpgradeChannel) {
|
||
try {
|
||
logger.info('get pre release version from github', channel)
|
||
const responses = await fetch('https://api.github.com/repos/CherryHQ/cherry-studio/releases?per_page=8', {
|
||
headers: {
|
||
Accept: 'application/vnd.github+json',
|
||
'X-GitHub-Api-Version': '2022-11-28',
|
||
'Accept-Language': 'en-US,en;q=0.9'
|
||
}
|
||
})
|
||
const data = (await responses.json()) as GithubReleaseInfo[]
|
||
const release: GithubReleaseInfo | undefined = data.find((item: GithubReleaseInfo) => {
|
||
return item.prerelease && item.tag_name.includes(`-${channel}.`)
|
||
})
|
||
|
||
logger.info('release info', release)
|
||
|
||
if (!release) {
|
||
return null
|
||
}
|
||
|
||
logger.info('release info', release.tag_name)
|
||
return `https://github.com/CherryHQ/cherry-studio/releases/download/${release.tag_name}`
|
||
} catch (error) {
|
||
logger.error('Failed to get latest not draft version from github:', error)
|
||
return null
|
||
}
|
||
}
|
||
|
||
private async _getIpCountry() {
|
||
try {
|
||
// add timeout using AbortController
|
||
const controller = new AbortController()
|
||
const timeoutId = setTimeout(() => controller.abort(), 5000)
|
||
|
||
const ipinfo = await fetch('https://ipinfo.io/json', {
|
||
signal: controller.signal,
|
||
headers: {
|
||
'User-Agent':
|
||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||
'Accept-Language': 'en-US,en;q=0.9'
|
||
}
|
||
})
|
||
|
||
clearTimeout(timeoutId)
|
||
const data = await ipinfo.json()
|
||
return data.country || 'CN'
|
||
} catch (error) {
|
||
logger.error('Failed to get ipinfo:', error)
|
||
return 'CN'
|
||
}
|
||
}
|
||
|
||
public setAutoUpdate(isActive: boolean) {
|
||
autoUpdater.autoDownload = isActive
|
||
autoUpdater.autoInstallOnAppQuit = isActive
|
||
}
|
||
|
||
private _getChannelByVersion(version: string) {
|
||
if (version.includes(`-${UpgradeChannel.BETA}.`)) {
|
||
return UpgradeChannel.BETA
|
||
}
|
||
if (version.includes(`-${UpgradeChannel.RC}.`)) {
|
||
return UpgradeChannel.RC
|
||
}
|
||
return UpgradeChannel.LATEST
|
||
}
|
||
|
||
private _getTestChannel() {
|
||
const currentChannel = this._getChannelByVersion(app.getVersion())
|
||
const savedChannel = configManager.getTestChannel()
|
||
|
||
if (currentChannel === UpgradeChannel.LATEST) {
|
||
return savedChannel || UpgradeChannel.RC
|
||
}
|
||
|
||
if (savedChannel === currentChannel) {
|
||
return savedChannel
|
||
}
|
||
|
||
// if the upgrade channel is not equal to the current channel, use the latest channel
|
||
return UpgradeChannel.LATEST
|
||
}
|
||
|
||
private async _setFeedUrl() {
|
||
const testPlan = configManager.getTestPlan()
|
||
if (testPlan) {
|
||
const channel = this._getTestChannel()
|
||
|
||
if (channel === UpgradeChannel.LATEST) {
|
||
this.autoUpdater.channel = UpgradeChannel.LATEST
|
||
this.autoUpdater.setFeedURL(FeedUrl.GITHUB_LATEST)
|
||
return
|
||
}
|
||
|
||
const preReleaseUrl = await this._getPreReleaseVersionFromGithub(channel)
|
||
if (preReleaseUrl) {
|
||
this.autoUpdater.setFeedURL(preReleaseUrl)
|
||
this.autoUpdater.channel = channel
|
||
return
|
||
}
|
||
|
||
// if no prerelease url, use lowest prerelease version to avoid error
|
||
this.autoUpdater.setFeedURL(FeedUrl.PRERELEASE_LOWEST)
|
||
this.autoUpdater.channel = UpgradeChannel.LATEST
|
||
return
|
||
}
|
||
|
||
this.autoUpdater.channel = UpgradeChannel.LATEST
|
||
this.autoUpdater.setFeedURL(FeedUrl.PRODUCTION)
|
||
|
||
const ipCountry = await this._getIpCountry()
|
||
logger.info('ipCountry', ipCountry)
|
||
if (ipCountry.toLowerCase() !== 'cn') {
|
||
this.autoUpdater.setFeedURL(FeedUrl.GITHUB_LATEST)
|
||
}
|
||
}
|
||
|
||
public cancelDownload() {
|
||
this.cancellationToken.cancel()
|
||
this.cancellationToken = new CancellationToken()
|
||
if (this.autoUpdater.autoDownload) {
|
||
this.updateCheckResult?.cancellationToken?.cancel()
|
||
}
|
||
}
|
||
|
||
public async checkForUpdates() {
|
||
if (isWin && 'PORTABLE_EXECUTABLE_DIR' in process.env) {
|
||
return {
|
||
currentVersion: app.getVersion(),
|
||
updateInfo: null
|
||
}
|
||
}
|
||
|
||
await this._setFeedUrl()
|
||
|
||
// disable downgrade after change the channel
|
||
this.autoUpdater.allowDowngrade = false
|
||
|
||
// github and gitcode don't support multiple range download
|
||
this.autoUpdater.disableDifferentialDownload = true
|
||
|
||
try {
|
||
this.updateCheckResult = await this.autoUpdater.checkForUpdates()
|
||
if (this.updateCheckResult?.isUpdateAvailable && !this.autoUpdater.autoDownload) {
|
||
// 如果 autoDownload 为 false,则需要再调用下面的函数触发下
|
||
// do not use await, because it will block the return of this function
|
||
logger.info('downloadUpdate manual by check for updates', this.cancellationToken)
|
||
this.autoUpdater.downloadUpdate(this.cancellationToken)
|
||
}
|
||
|
||
return {
|
||
currentVersion: this.autoUpdater.currentVersion,
|
||
updateInfo: this.updateCheckResult?.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 GithubReleaseInfo {
|
||
draft: boolean
|
||
prerelease: boolean
|
||
tag_name: string
|
||
}
|
||
interface ReleaseNoteInfo {
|
||
readonly version: string
|
||
readonly note: string | null
|
||
}
|