'init'
Some checks failed
Upstream Sync / Sync latest commits from upstream repo (push) Has been cancelled

This commit is contained in:
eric
2026-03-24 14:46:41 -05:00
parent c5a1a9a404
commit b77a8cadb2
164 changed files with 57060 additions and 58 deletions

View File

@@ -0,0 +1,224 @@
/**
* Electron app 模块:开机启动、跳转列表、关于面板、日志路径、角标、优雅重启、指标等。
* 文档:https://www.electronjs.org/docs/latest/api/app
*/
import { app, BrowserWindow, ipcMain, shell } from 'electron'
import fs from 'fs'
import path from 'path'
const VALID_NAV = new Set(['desk', 'record', 'network', 'proxy', 'system', 'wechat'])
export function parseNavFromArgv(argv: string[]): string | null {
for (const a of argv) {
const m = /^--nav=(.+)$/.exec(a)
if (!m) continue
const id = m[1].trim()
if (VALID_NAV.has(id)) return id
}
return null
}
/** 须在 app.ready 之前调用(Windows Toast / 任务栏分组) */
export function setAppUserModelIdEarly(): void {
if (process.platform !== 'win32') return
try {
app.setAppUserModelId('com.douyin.recorder.console')
} catch {
/* ignore */
}
}
function readPackageJsonField(field: 'description' | 'version'): string {
try {
const candidates = [
path.join(app.getAppPath(), 'package.json'),
path.join(process.cwd(), 'package.json'),
]
for (const p of candidates) {
if (fs.existsSync(p)) {
const j = JSON.parse(fs.readFileSync(p, 'utf-8')) as Record<string, unknown>
const v = j[field]
if (typeof v === 'string') return v
}
}
} catch {
/* ignore */
}
return ''
}
/** ready 后立即调用 */
export function applyAppHostBootstrap(getBackendRoot: () => string): void {
try {
const logDir = path.join(app.getPath('userData'), 'logs')
app.setAppLogsPath(logDir)
} catch {
/* ignore */
}
const ver = app.getVersion()
const desc = readPackageJsonField('description') || '直播录制与转播控制台'
app.setAboutPanelOptions({
applicationName: app.getName(),
applicationVersion: ver,
copyright: `Copyright © ${new Date().getFullYear()}`,
credits: desc,
})
if (process.env.WEB2_SECURE_DNS === '1') {
try {
app.configureHostResolver({ secureDnsMode: 'automatic' })
} catch {
/* ignore */
}
}
const proxy = process.env.WEB2_ELECTRON_GLOBAL_PROXY?.trim()
if (proxy) {
try {
void app.setProxy({ proxyRules: proxy })
} catch {
/* ignore */
}
}
if (process.platform === 'win32') {
try {
app.setUserTasks([
{
program: process.execPath,
arguments: '--nav=desk',
title: '工作台',
description: '打开工作台仪表盘',
iconPath: process.execPath,
iconIndex: 0,
},
{
program: process.execPath,
arguments: '--nav=record',
title: '录制',
description: '打开录制与转播控制',
iconPath: process.execPath,
iconIndex: 0,
},
{
program: process.execPath,
arguments: '--nav=network',
title: '网络',
description: '网络与远程绑定',
iconPath: process.execPath,
iconIndex: 0,
},
])
} catch {
/* ignore */
}
}
}
export function registerAppHostIpcHandlers(getBackendRoot: () => string): void {
ipcMain.removeHandler('app:get-login-item')
ipcMain.handle('app:get-login-item', () => {
try {
return app.getLoginItemSettings()
} catch {
return { openAtLogin: false }
}
})
ipcMain.removeHandler('app:set-login-item')
ipcMain.handle('app:set-login-item', (_evt, openAtLogin: unknown) => {
if (typeof openAtLogin !== 'boolean') return { ok: false as const, error: '参数无效' }
try {
app.setLoginItemSettings({
openAtLogin,
path: process.execPath,
args: [],
})
return { ok: true as const }
} catch (e) {
return { ok: false as const, error: e instanceof Error ? e.message : String(e) }
}
})
ipcMain.removeHandler('app:relaunch')
ipcMain.handle('app:relaunch', () => {
app.relaunch()
app.exit(0)
})
ipcMain.removeHandler('app:get-metrics')
ipcMain.handle('app:get-metrics', () => {
try {
return app.getAppMetrics()
} catch {
return []
}
})
ipcMain.removeHandler('app:show-about')
ipcMain.handle('app:show-about', () => {
try {
app.showAboutPanel()
} catch {
/* ignore */
}
})
ipcMain.removeHandler('app:set-badge')
ipcMain.handle('app:set-badge', (_evt, count: unknown) => {
const n = typeof count === 'number' && Number.isFinite(count) ? Math.max(0, Math.floor(count)) : 0
try {
if (process.platform === 'darwin' || process.platform === 'linux') {
app.setBadgeCount(n)
}
} catch {
/* Windows 无 dock 角标,忽略 */
}
})
ipcMain.removeHandler('app:get-paths')
ipcMain.handle('app:get-paths', () => {
const br = getBackendRoot()
return {
userData: app.getPath('userData'),
logs: app.getPath('logs'),
temp: app.getPath('temp'),
backendRoot: br,
backendConfig: path.join(br, 'config'),
}
})
ipcMain.removeHandler('app:add-recent-config')
ipcMain.handle('app:add-recent-config', () => {
const cfg = path.join(getBackendRoot(), 'config')
try {
if (fs.existsSync(cfg)) {
app.addRecentDocument(cfg)
}
} catch {
/* ignore */
}
})
ipcMain.removeHandler('app:open-logs-folder')
ipcMain.handle('app:open-logs-folder', async () => {
const p = app.getPath('logs')
try {
await fs.promises.mkdir(p, { recursive: true })
} catch {
/* ignore */
}
const err = await shell.openPath(p)
if (err) throw new Error(err)
})
}
export function sendNavToWindow(win: BrowserWindow, nav: string): void {
if (!VALID_NAV.has(nav)) return
try {
win.webContents.send('app:nav', nav)
} catch {
/* ignore */
}
}