feat(w5): ship v2.0.0 platform sync, undo persistence, and updates

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 18:24:51 +08:00
parent cb6b4c3731
commit fe421ffc55
60 changed files with 2207 additions and 36 deletions
+98
View File
@@ -0,0 +1,98 @@
import { app, BrowserWindow } from 'electron'
import electronUpdater from 'electron-updater'
import { IPC } from '../../shared/ipc-channels'
const { autoUpdater } = electronUpdater
export interface UpdateInfoPayload {
version: string
releaseNotes?: string
}
export interface UpdateProgressPayload {
percent: number
transferred: number
total: number
}
export class UpdateService {
private disabled: boolean
private mockVersion: string | null = null
constructor() {
if (process.env.BILIN_MOCK_UPDATE === '1') {
this.disabled = false
this.mockVersion = process.env.BILIN_MOCK_UPDATE_VERSION ?? '9.9.9'
return
}
this.disabled =
process.env.BILIN_E2E === '1' ||
!app.isPackaged ||
process.env.NODE_ENV === 'development'
autoUpdater.autoDownload = false
autoUpdater.autoInstallOnAppQuit = true
autoUpdater.on('update-available', (info) => {
this.broadcast(IPC.UPDATE_AVAILABLE, {
version: info.version,
releaseNotes: typeof info.releaseNotes === 'string' ? info.releaseNotes : undefined
})
})
autoUpdater.on('download-progress', (p) => {
this.broadcast(IPC.UPDATE_PROGRESS, {
percent: p.percent,
transferred: p.transferred,
total: p.total
})
})
autoUpdater.on('update-downloaded', (info) => {
this.broadcast(IPC.UPDATE_DOWNLOADED, { version: info.version })
})
autoUpdater.on('error', (err) => {
this.broadcast(IPC.UPDATE_ERROR, { message: err.message })
})
}
async check(): Promise<UpdateInfoPayload | null> {
if (this.disabled) return null
if (this.mockVersion) {
const payload = { version: this.mockVersion, releaseNotes: 'Mock update' }
this.broadcast(IPC.UPDATE_AVAILABLE, payload)
return payload
}
const result = await autoUpdater.checkForUpdates()
const info = result?.updateInfo
if (!info) return null
return {
version: info.version,
releaseNotes: typeof info.releaseNotes === 'string' ? info.releaseNotes : undefined
}
}
async download(): Promise<void> {
if (this.disabled) return
if (this.mockVersion) {
this.broadcast(IPC.UPDATE_PROGRESS, { percent: 50, transferred: 50, total: 100 })
this.broadcast(IPC.UPDATE_DOWNLOADED, { version: this.mockVersion })
return
}
await autoUpdater.downloadUpdate()
}
install(): void {
if (this.disabled || this.mockVersion) return
autoUpdater.quitAndInstall()
}
private broadcast(channel: string, payload: unknown): void {
try {
for (const win of BrowserWindow.getAllWindows()) {
win.webContents.send(channel, payload)
}
} catch {
/* test env */
}
}
}