142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
// 主进程入口
|
|
import { app, dialog, BrowserWindow } from 'electron'
|
|
import { join } from 'node:path'
|
|
import { existsSync, writeFileSync, readFileSync } from 'node:fs'
|
|
import { randomUUID } from 'node:crypto'
|
|
import { hostname, platform } from 'node:os'
|
|
|
|
import { paths } from './paths'
|
|
import { getSettings, ensureDownloadDir } from './settings'
|
|
import { ensureIconFile } from './icons'
|
|
import { createMainWindow, broadcastToRenderer } from './window'
|
|
import { createTray } from './tray'
|
|
import { Discovery } from './discovery'
|
|
import { ChatServer } from './chat-server'
|
|
import { ChatClient } from './chat-client'
|
|
import { FileServer } from './file-server'
|
|
import { TerminalManager } from './remote/terminal'
|
|
import { ForwardManager } from './remote/forward'
|
|
import { UsbBridgeManager } from './remote/usb'
|
|
import { registerIpc, bindNetworkContext } from './ipc'
|
|
import { listDevices, totalUnread, getUnread, pruneAudit } from './db'
|
|
import { setBadgeCount } from './notify'
|
|
import { DEFAULT_PORTS, PROTOCOL_VERSION } from './protocol'
|
|
import type { DeviceInfo } from './protocol'
|
|
|
|
const gotLock = app.requestSingleInstanceLock()
|
|
if (!gotLock) {
|
|
app.quit()
|
|
} else {
|
|
;(global as any).__quitting = false
|
|
app.on('second-instance', () => {
|
|
const w = BrowserWindow.getAllWindows()[0]
|
|
if (w) { if (w.isMinimized()) w.restore(); w.show(); w.focus() }
|
|
})
|
|
|
|
app.whenReady().then(main).catch((e) => {
|
|
console.error('startup error', e)
|
|
dialog.showErrorBox('启动失败', String(e?.stack || e))
|
|
app.quit()
|
|
})
|
|
}
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (!(global as any).__quitting) {
|
|
// 不退出, 隐藏到托盘
|
|
}
|
|
})
|
|
|
|
app.on('before-quit', () => { (global as any).__quitting = true })
|
|
app.on('will-quit', async () => { /* 网络资源由各模块 stop */ })
|
|
|
|
let stopFns: Array<() => Promise<void> | void> = []
|
|
|
|
async function main() {
|
|
ensureIconFile(paths.iconPng, 32)
|
|
ensureDownloadDir()
|
|
setBadgeCount(totalUnread())
|
|
|
|
const self = buildSelf()
|
|
const discovery = new Discovery(self)
|
|
const chatServer = new ChatServer(DEFAULT_PORTS.chat)
|
|
const chatClient = new ChatClient(self)
|
|
const fileServer = new FileServer(DEFAULT_PORTS.file, ensureDownloadDir())
|
|
const terminal = new TerminalManager(chatClient, self.deviceId)
|
|
const forward = new ForwardManager(chatClient)
|
|
const usb = new UsbBridgeManager(chatClient)
|
|
|
|
const chatPort = await chatServer.start()
|
|
const filePort = await fileServer.start()
|
|
self.chatPort = chatPort
|
|
self.filePort = filePort
|
|
// 后台清理 24h+ 残留 .tmp (不阻塞启动)
|
|
fileServer.cleanupStaleTmp().catch(() => {})
|
|
stopFns.push(
|
|
() => chatServer.stop(),
|
|
() => chatClient.stop(),
|
|
() => discovery.stop(),
|
|
() => fileServer.stop(),
|
|
() => terminal.stop(),
|
|
() => forward.stop(),
|
|
() => usb.stop(),
|
|
)
|
|
|
|
await discovery.start()
|
|
bindNetworkContext({ self, discovery, chatServer, chatClient, fileServer, terminal, forward, usb })
|
|
|
|
// 已入库的设备: 启动后主动尝试连接
|
|
for (const d of listDevices()) {
|
|
if (d.device_id === self.deviceId) continue
|
|
chatClient.connectTo({
|
|
deviceId: d.device_id,
|
|
name: d.name,
|
|
hostname: d.hostname || '',
|
|
platform: d.platform || '',
|
|
appVersion: d.app_version || '',
|
|
address: d.last_ip || '127.0.0.1',
|
|
chatPort: DEFAULT_PORTS.chat,
|
|
filePort: DEFAULT_PORTS.file,
|
|
version: PROTOCOL_VERSION,
|
|
ts: d.last_seen,
|
|
} as DeviceInfo)
|
|
}
|
|
|
|
createMainWindow()
|
|
createTray()
|
|
registerIpc()
|
|
|
|
// 启动时按 auditRetentionDays 清理一次旧审计
|
|
const s = getSettings()
|
|
const retentionMs = Math.max(1, s.auditRetentionDays) * 24 * 3600 * 1000
|
|
pruneAudit(retentionMs)
|
|
|
|
setTimeout(pushInitialState, 400)
|
|
}
|
|
|
|
function buildSelf(): DeviceInfo {
|
|
const idFile = join(paths.appData, 'device.id')
|
|
let id: string
|
|
if (existsSync(idFile)) id = readFileSync(idFile, 'utf8').trim()
|
|
else { id = randomUUID(); writeFileSync(idFile, id, 'utf8') }
|
|
const s = getSettings()
|
|
return {
|
|
deviceId: id,
|
|
name: s.deviceName || hostname() || 'User',
|
|
hostname: hostname() || '',
|
|
platform: platform(),
|
|
version: PROTOCOL_VERSION,
|
|
appVersion: app.getVersion(),
|
|
address: '0.0.0.0',
|
|
chatPort: 0,
|
|
filePort: 0,
|
|
ts: Date.now(),
|
|
}
|
|
}
|
|
|
|
function pushInitialState() {
|
|
broadcastToRenderer('boot:ready', {
|
|
settings: getSettings(),
|
|
unread: getUnread(),
|
|
})
|
|
}
|