6b3cd288b1
- 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>
196 lines
5.3 KiB
JavaScript
196 lines
5.3 KiB
JavaScript
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',
|
|
uat: 'http://nas.wjl-work.top:7034',
|
|
}
|
|
|
|
function getWindowUrl() {
|
|
const envArg = process.argv.find(a => a.startsWith('--env='))
|
|
if (envArg) {
|
|
const env = envArg.split('=')[1]
|
|
return ENV_URLS[env] || ENV_URLS.dev
|
|
}
|
|
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,
|
|
height: 800,
|
|
minWidth: 960,
|
|
minHeight: 600,
|
|
title: 'Game Group',
|
|
autoHideMenuBar: true,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
contextIsolation: true,
|
|
webSecurity: false,
|
|
allowRunningInsecureContent: true,
|
|
},
|
|
})
|
|
|
|
mainWindow = win
|
|
|
|
const url = getWindowUrl()
|
|
win.loadURL(url)
|
|
|
|
// 页面标题同步
|
|
win.on('page-title-updated', (event, title) => {
|
|
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(() => {
|
|
// 自动批准麦克风/摄像头权限请求,无需用户手动确认
|
|
session.defaultSession.setPermissionRequestHandler((_webContents, permission, callback) => {
|
|
if (permission === 'media' || permission === 'microphone' || permission === 'camera') {
|
|
callback(true)
|
|
} else {
|
|
callback(false)
|
|
}
|
|
})
|
|
|
|
// 绕过权限检查,确保 mediaDevices 可用
|
|
session.defaultSession.setPermissionCheckHandler((_webContents, permission) => {
|
|
if (permission === 'media' || permission === 'microphone' || permission === 'camera') {
|
|
return true
|
|
}
|
|
return false
|
|
})
|
|
|
|
const win = createWindow()
|
|
setupAutoUpdater(win)
|
|
})
|
|
|
|
app.on('window-all-closed', () => {
|
|
app.quit()
|
|
})
|