5.1 KiB
LocalNetMsg Agent Guide
LAN instant-messaging desktop app. Stack: Electron 32 + Vue 3 + TypeScript + Vite + Pinia + Element Plus + SQLite (better-sqlite3). Three independent processes built by electron-vite.
Commands
| Task | Command |
|---|---|
| Dev (Electron + HMR) | npm run dev |
| Build (no installer) | npm run build |
| Typecheck main+preload | npm run typecheck:node |
| Typecheck renderer | npm run typecheck:web |
| Typecheck both | npm run typecheck |
| Windows installer | npm run package:win → dist-Electron\LocalNetMsg-0.1.0-Setup.exe |
| macOS / Linux | npm run package:mac / npm run package:linux |
| Unpacked dir only | npm run package:dir |
All package:* scripts bake in ELECTRON_MIRROR + ELECTRON_BUILDER_BINARIES_MIRROR (npmmirror.com). Do not remove unless on the open internet.
No test suite. Verify changes via npm run typecheck + manual npm run dev.
Layout
src/
main/ Electron main process (Node) — entry: index.ts
discovery.ts UDP broadcast beacon (multi-NIC, per-interface subnet bcast)
chat-server.ts WebSocketServer on 0.0.0.0:47900
chat-client.ts Outgoing WS with backoff reconnect
file-server.ts HTTP /upload + /file, default cap 100GB
db.ts SQLite via better-sqlite3 (data/app.db)
settings.ts electron-store wrapper (~/AppData/Roaming/local-net-msg/config.json)
notify.ts System Notification + Windows overlay badge
ipc.ts All ipcMain.handle() wiring
protocol.ts MessageEnvelope, WsFrame, DeviceInfo types
preload/ contextBridge exposing typed API to renderer
renderer/ Vue 3 SPA (Vite root)
src/
main.ts createApp + Pinia + ElementPlus (full import) + zhCn
App.vue Global event listeners (device:*, message:*, settings:*)
components/ Sidebar, ChatView, MessageInput, MessageItem, SettingsView, MarkdownView
stores/ Pinia: device.ts, message.ts, session.ts
api.ts IPC typings (mirrors preload)
utils/ format.ts (colorFor, initialsOf, formatSize, ...)
electron.vite.config.ts Three builds → out/{main,preload,renderer}/
Renderer alias: @ → src/renderer/src.
Network protocol
UDP discovery on 47800, chat WS on 47900, file HTTP on 47901. All three bind 0.0.0.0 and auto-shift on conflict.
DeviceInfo carries address (chosen from the first non-internal IPv4 of the responding NIC) plus chatPort/filePort. The chat server sends no broadcast of its own — discovery comes purely from UDP. Receiver uses rinfo.address (real source IP), never the payload's self.address.
Hard-won pitfalls (don't repeat these)
-
chat-clientMUST have anon('error')listener. Node EventEmitter throwsERR_UNHANDLED_ERRORand kills the main process if'error'fires with no listener. The WS'error'event is followed by'close'which triggers reconnect. Don't try to "fix" by emitting on the client; justconsole.warnand move on. -
Drag-drop file path. Electron 32 removed
File.path. UsewebUtils.getPathForFile(file)fromelectron— exposed insrc/preload/index.tsasgetPathForFile(file). Callers must read it viawindow.api.getPathForFile(f)BEFORE the await boundary. -
Native rebuild.
postinstallrunselectron-builder install-app-depsto rebuildbetter-sqlite3against the bundled Electron ABI. Afternpm ior upgrading Electron, this must run. If you forget, sqlite open will throw on launch. -
mkdirfor Windows reserved names. Output dir isdist-Electron\(escaped;dist\electronchokes on Windows). Don't rename without verifying the build still produces a valid path. -
nsisisoneClick: false. Installer asks for install path + creates Desktop/Start-Menu shortcuts. Permissions are user-scoped. -
Renderer global drag highlight. Track
dragenter/dragleavewith a depth counter (not a boolean) — child elements fire leave/enter pairs. SeeMessageInput.vue'sdragDepth. -
Try not to leak Electron internals to UI. Chat-client WS state stays in main; renderer only sees discovery online/offline. Adding a "chat not connected" pill in the UI was reverted for a reason — keep the surface area to two states (在线/离线).
-
File conflict naming. Append
_Nbefore the first.:archive.tar.gz→archive_1.tar.gz,.bashrc→.bashrc_1. Logic infile-server.tstargetPath().
Configuration knobs
LNM_MAX_FILE_SIZE=<bytes>env var overrides the 100 GB default infile-server.ts. Useful for testing the limit path.deviceName,downloadDir,notifications,autoStartlive in~/AppData/Roaming/local-net-msg/config.jsonviaelectron-store. Edit the file directly while dev is stopped.data/app.db— SQLite DB. DELETE to reset all devices/messages. Auto-migrates on next launch.
Packaging gotcha
release/ contains an app.asar Windows cannot delete while the previous app instance holds a file handle. Build now writes to dist-Electron\ to dodge this. If you ever switch back, kill the running app first or you'll get EPERM.