Files
LocalNetMsg/src/main/index.ts
T

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(),
})
}