initial: LAN IM desktop app (Electron 32 + Vue 3 + TS + SQLite)
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
// 主进程入口
|
||||
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 { registerIpc, bindNetworkContext } from './ipc'
|
||||
import { listDevices, totalUnread, getUnread } 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 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())
|
||||
|
||||
await discovery.start()
|
||||
bindNetworkContext({ self, discovery, chatServer, chatClient, fileServer })
|
||||
|
||||
// 已入库的设备: 启动后主动尝试连接
|
||||
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()
|
||||
|
||||
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(),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user