Some checks failed
Upstream Sync / Sync latest commits from upstream repo (push) Has been cancelled
225 lines
5.7 KiB
TypeScript
225 lines
5.7 KiB
TypeScript
/**
|
||
* 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 */
|
||
}
|
||
}
|