feat(electron): add persistent auth, auto-updater, and NAS copy script

- Integrate electron-store for persistent PocketBase auth backup
  across localStorage clears and app reinstalls
- Switch build target from portable to nsis to generate latest.yml
  for electron-updater generic provider
- Add user confirmation dialogs before download and before install
- Add post-build script to copy .exe/.yml/.nupkg to NAS share and
  local electron-update/ directory for nginx volume mount
- Mount ./electron-update into frontend nginx containers via
  docker-compose for automatic update file serving

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wjl
2026-04-21 14:38:48 +08:00
parent d3ef22f06e
commit 6b3cd288b1
11 changed files with 318 additions and 16 deletions
+125 -2
View File
@@ -1,5 +1,22 @@
const { app, BrowserWindow, session } = require('electron')
const { app, BrowserWindow, session, dialog, ipcMain } = require('electron')
const { autoUpdater } = require('electron-updater')
const path = require('path')
const Store = require('electron-store')
const store = new Store()
// 同步 IPC:供 preload 读写持久化数据
ipcMain.on('store-get-sync', (event, key) => {
event.returnValue = store.get(key)
})
ipcMain.on('store-set-sync', (event, key, value) => {
store.set(key, value)
event.returnValue = true
})
ipcMain.on('store-delete-sync', (event, key) => {
store.delete(key)
event.returnValue = true
})
const ENV_URLS = {
dev: 'http://192.168.1.14:7033',
@@ -15,11 +32,19 @@ function getWindowUrl() {
return ENV_URLS.dev
}
function getEnvName() {
const url = getWindowUrl()
if (url.includes('7034')) return 'uat'
return 'dev'
}
// 在 Chromium 启动前将 HTTP 内网地址标记为安全源,
// 否则 navigator.mediaDevices 在 HTTP 非 localhost 下会被置空
const insecureOrigins = Object.values(ENV_URLS).join(',')
app.commandLine.appendSwitch('unsafely-treat-insecure-origin-as-secure', insecureOrigins)
let mainWindow = null
function createWindow() {
const win = new BrowserWindow({
width: 1280,
@@ -36,6 +61,8 @@ function createWindow() {
},
})
mainWindow = win
const url = getWindowUrl()
win.loadURL(url)
@@ -44,6 +71,101 @@ function createWindow() {
event.preventDefault()
win.setTitle(`Game Group - ${title}`)
})
win.on('closed', () => {
mainWindow = null
})
return win
}
// 自动更新配置
function setupAutoUpdater(win) {
const env = getEnvName()
const feedUrl = `${ENV_URLS[env]}/electron-update/`
autoUpdater.setFeedURL({ provider: 'generic', url: feedUrl })
autoUpdater.autoDownload = false
autoUpdater.autoInstallOnAppQuit = false
autoUpdater.on('checking-for-update', () => {
console.log('[updater] Checking for update at', feedUrl)
})
autoUpdater.on('update-available', (info) => {
console.log('[updater] Update available:', info.version)
if (win && !win.isDestroyed()) {
win.webContents.send('update-available', info)
dialog.showMessageBox(win, {
type: 'info',
title: '发现新版本',
message: `发现新版本 ${info.version},是否立即下载更新?`,
buttons: ['立即下载', '稍后再说'],
defaultId: 0,
cancelId: 1,
}).then(({ response }) => {
if (response === 0) {
autoUpdater.downloadUpdate().catch((err) => {
console.error('[updater] Download failed:', err.message)
})
}
})
}
})
autoUpdater.on('update-not-available', () => {
console.log('[updater] No update available')
})
autoUpdater.on('error', (err) => {
console.error('[updater] Error:', err.message)
if (win && !win.isDestroyed()) {
win.webContents.send('update-error', err.message)
}
})
autoUpdater.on('download-progress', (progress) => {
console.log(`[updater] Download progress: ${progress.percent.toFixed(1)}%`)
if (win && !win.isDestroyed()) {
win.webContents.send('download-progress', progress)
}
})
autoUpdater.on('update-downloaded', (info) => {
console.log('[updater] Update downloaded:', info.version)
if (win && !win.isDestroyed()) {
win.webContents.send('update-downloaded', info)
dialog.showMessageBox(win, {
type: 'info',
title: '更新就绪',
message: `新版本 ${info.version} 已下载完成,是否立即重启安装?`,
buttons: ['立即重启', '稍后'],
defaultId: 0,
}).then(({ response }) => {
if (response === 0) {
autoUpdater.quitAndInstall()
}
})
}
})
// 手动触发检查更新(供前端调用)
ipcMain.on('check-for-updates', () => {
autoUpdater.checkForUpdates().catch((err) => {
console.error('[updater] Manual check failed:', err.message)
})
})
ipcMain.on('quit-and-install', () => {
autoUpdater.quitAndInstall()
})
// 启动后延迟 5 秒检查更新(等窗口稳定后再检查)
setTimeout(() => {
autoUpdater.checkForUpdates().catch((err) => {
console.error('[updater] Initial check failed:', err.message)
})
}, 5000)
}
app.whenReady().then(() => {
@@ -64,7 +186,8 @@ app.whenReady().then(() => {
return false
})
createWindow()
const win = createWindow()
setupAutoUpdater(win)
})
app.on('window-all-closed', () => {