Internal documentation — for collaborators and the development team only. Do not distribute or publish this document publicly.
- Overview
- Architecture
- How It Works
- Tech Stack
- Project Structure
- Component Reference
- Backend Flow
- IPC API
- Network Diagram
- Config Files
- Development Setup
- Build & Package
- Known Issues & Notes
- Team
PingPuff is a Windows desktop VPN client built with Electron + React. It provides a one-click interface for connecting through a DPI-bypass proxy chain:
Your Device → NexNull (SNI Spoofing) → ISP/DPI (bypassed) → Target Server
↑
Xray (SOCKS/HTTP proxy)
The app handles the full lifecycle automatically:
- Downloads and extracts the core binaries on first run
- Writes all config files
- Starts the SNI bypass process (
nexnull.exe) - Starts the Xray proxy core (
xray.exe) - Sets the Windows system SOCKS proxy to
127.0.0.1:10808 - Clears the proxy and kills all processes on disconnect or close
┌─────────────────────────────────────────────────────────┐
│ Electron Main Process │
│ (electron/main.js) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Admin Check│ │ Download/ │ │ Process Mgmt │ │
│ │ (net sess) │ │ Extract Core │ │ SNI + Xray │ │
│ └─────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ IPC Bridge (preload.js) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ contextBridge
┌─────────────────────────────────────────────────────────┐
│ Renderer Process (React) │
│ │
│ App.jsx → SetupScreen (splash) → Dashboard (main UI) │
│ │
│ VpnContext ──── connect() / disconnect() │
│ NetworkGraph ── animated SVG diagram │
│ TitleBar ────── logo + info button + window controls │
└─────────────────────────────────────────────────────────┘
- Admin check —
net sessiondetects elevation. If not admin, PowerShell relaunches withStart-Process -Verb RunAs(UAC prompt). - SetupScreen renders immediately — shows logo + progress bar.
- Directory check — creates
~/Documents/PingPuff/if missing. - Download — fetches
https://my.uupload.ir/dl/1L06V910ascore.zip. Contains:xray.exeat rootsni/nexnull.exein thesni/subfolder
- Extract — PowerShell
Expand-ArchiveintoPingPuff/. Uses recursivefindFile()to locate binaries regardless of zip structure. - Config write — writes
config.json(Xray) andsni/config.json(NexNull) fresh every launch. - Start NexNull — spawns
nexnull.exe, sends\nto stdin to bypass the interactive startup prompt. - Start Xray — spawns
xray.exe run -c config.json, buffers all output to detect success vs failure. - Set proxy — writes
socks=127.0.0.1:10808to Windows registry + notifies WinINet. - Dashboard fades in.
- Connect — kills any stale processes by PID +
taskkill /F /IM, rewrites configs, restarts NexNull → Xray → sets proxy. - Disconnect —
taskkill /Fby PID and by name, clears registry proxy, notifies WinINet. - Window close — same as disconnect, then
app.quit().
NexNull intercepts the TCP handshake at kernel level using WinDivert. It injects a fake TLS ClientHello with SNI = hcaptcha.com (a whitelisted domain). The DPI system sees the allowed domain and permits the connection. The real server ignores the out-of-window packet. Full bidirectional relay then starts.
Config: LISTEN_PORT=40443, CONNECT_IP=104.19.229.21:443, FAKE_SNI=hcaptcha.com
Xray runs a local SOCKS5 proxy on 127.0.0.1:10808 and HTTP proxy on 127.0.0.1:10809. It routes traffic through the NexNull tunnel using Trojan protocol over WebSocket/TLS.
Protocol: Trojan → WS → TLS
Server: 127.0.0.1:40443 (NexNull)
SNI: www.gossipglove.com
Path: /assignment
Password: humanity
| Layer | Technology |
|---|---|
| Desktop shell | Electron 33 |
| Frontend | React 18 + Vite 6 |
| Styling | TailwindCSS 3 + inline styles |
| Package manager | pnpm |
| VPN core | Xray-core (Trojan/WS/TLS) |
| DPI bypass | NexNull / SNI-Spoofing (WinDivert) |
| Proxy setting | Windows Registry + WinINet |
| Build/package | electron-builder 25 |
PingPuff/
│
├── electron/
│ ├── main.js # Main process — admin check, download, process mgmt, IPC
│ └── preload.js # Context bridge — exposes electronAPI to renderer
│
├── src/
│ ├── App.jsx # Root — shows SetupScreen then Dashboard
│ ├── main.jsx # React entry point
│ ├── index.css # Global styles + keyframe animations
│ │
│ ├── components/
│ │ ├── TitleBar.jsx # Draggable title bar, logo, info button, window controls
│ │ ├── Dashboard.jsx # Main view — graph + magnet connect button
│ │ ├── NetworkGraph.jsx # Animated SVG network diagram (5-node SNI flow)
│ │ ├── SetupScreen.jsx # Splash screen with progress bar during setup
│ │ ├── ServerPicker.jsx # Server selection modal
│ │ └── AboutModal.jsx # About / credits modal (Persian)
│ │
│ └── context/
│ └── VpnContext.jsx # Global VPN state — connect/disconnect, stats, ping
│
├── resources/
│ └── sni/ # (legacy) bundled SNI resources — now downloaded
│
├── public/
│ ├── logo.png # App logo (shown in title bar + setup screen)
│ └── icon.ico # Windows app icon (generated from logo.png)
│
├── index.html
├── vite.config.js
├── tailwind.config.js
├── postcss.config.js
└── package.json
The entire backend. Key functions:
| Function | Purpose |
|---|---|
isAdmin() |
Checks net session for elevation |
relaunchAsAdmin() |
PowerShell Start-Process -Verb RunAs |
downloadFile(url, dest, onProgress) |
HTTPS download with redirect follow + progress callback |
extractZip(zipPath, destDir) |
PowerShell Expand-Archive |
findFile(dir, name) |
Recursive file search (handles any zip structure) |
writeSNIConfig() |
Writes sni/config.json |
startSNI() |
Spawns nexnull.exe, sends \n to stdin, 4s timeout |
startXray() |
Spawns xray.exe run -c config.json, buffers output, 5s timeout |
setSystemProxy() |
Registry write + WinINet notify |
clearSystemProxy() |
Registry clear + WinINet notify |
cleanup() |
taskkill /F by PID + by name, clears proxy |
runSetup(win) |
Full 8-step setup flow, sends IPC progress events |
dbg(tag, ...args) |
Timestamped console debug logger |
dbgErr(tag, ...args) |
Timestamped console error logger |
Exposes window.electronAPI to the renderer:
window.electronAPI = {
minimize() // window-minimize IPC
maximize() // window-maximize IPC
close() // window-close IPC
platform // process.platform string
onSetupStatus(cb) // setup:status event → { step, msg, progress }
onSetupDone(cb) // setup:done event
onSetupError(cb) // setup:error event → { msg }
vpnConnect() // invoke vpn:connect → { ok, error? }
vpnDisconnect() // invoke vpn:disconnect → { ok }
removeAllListeners(ch) // cleanup IPC listeners
}Global state provider. Exposes:
{
connected, connecting,
connect(), // calls electronAPI.vpnConnect() or simulates in dev
disconnect(), // calls electronAPI.vpnDisconnect()
selectedServer, setSelectedServer,
pingHistory, // last 24 ping values (ms)
bytesDown, bytesUp, sessionTime,
fmtTime(s), fmtBytes(b),
killSwitch, setKillSwitch,
}SVG-based animated network diagram showing the actual NexNull/SNI-Spoofing flow:
[You] ──── [NexNull :40443] ──── [WinDivert] ──── [DPI] ──── [Target]
│ ↑
└── fake SNI arc ───────────┘
(amber, hcaptcha.com)
- Green edges — main data path with animated lime packets when connected
- Amber arc — fake SNI bypass path from WinDivert over DPI to Target
- Neon glow — breathing multi-layer glow on Target node when connected
- Connecting animation — white→green linear fill on each edge, staggered delays
- DPI badge — shows
BYPASSED ✓/BLOCKINGstate
Full-screen splash shown during the 8-step setup. Receives setup:status IPC events and renders:
- Breathing logo with drop-shadow glow
- Step message text
- Smooth progress bar with shimmer
- Step indicator dots (active dot expands)
- Error state (red) with message
Main UI after setup. Contains:
NetworkGraphfilling the upper areaMagnetButton— the connect/disconnect pill button with rAF-driven cursor-tracking magnet effect- Random Persian connect labels from
FUNNY_LABELS[] ServerPickermodal (triggered by "change server" link — currently UI-only)
Custom frameless title bar:
logo.png+ "PingPuff" nameⓘinfo button → opensAboutModal- Minimize
—and Close×buttons WebkitAppRegion: dragfor window dragging
app launch
│
├─ isAdmin? ──No──→ relaunchAsAdmin() → UAC → restart
│
├─ createWindow()
│
├─ ready-to-show → show() → runSetup()
│
└─ runSetup()
│
├─ [1] mkdir ~/Documents/PingPuff
│
├─ [2] xray.exe && nexnull.exe exist?
│ No → downloadFile(DOWNLOAD_URL, core.zip)
│
├─ [3] extractZip(core.zip, PingPuff/)
│ findFile(xray.exe) → move to root if needed
│ findFile(nexnull.exe) → move to sni/ if needed
│
├─ [4] writeFile(config.json) ← Xray Trojan/WS/TLS config
│
├─ [5] writeSNIConfig() ← NexNull config
│
├─ [6] startSNI()
│ spawn nexnull.exe
│ stdin.write('\n') ← bypass Press Enter prompt
│ wait 4s or "NexNull" in stdout
│
├─ [7] startXray()
│ spawn xray.exe run -c config.json
│ buffer stdout, detect "started" vs "failed"
│ 5s timeout fallback
│
├─ [8] setSystemProxy()
│ reg add ProxyEnable=1
│ reg add ProxyServer=socks=127.0.0.1:10808
│ InternetSetOption notify
│
└─ send setup:done → renderer fades in Dashboard
| Event | Payload | When |
|---|---|---|
setup:status |
{ step, msg, progress } |
Each setup step |
setup:done |
— | Setup complete |
setup:error |
{ msg } |
Fatal setup error |
| Channel | Returns | Action |
|---|---|---|
vpn:connect |
{ ok, error? } |
Kill stale procs, restart SNI+Xray, set proxy |
vpn:disconnect |
{ ok } |
Kill all procs, clear proxy |
| Channel | Action |
|---|---|
window-minimize |
win.minimize() |
window-maximize |
win.maximize() / unmaximize() |
window-close |
cleanup() + win.close() |
CLIENT (You)
│
│ SOCKS5 127.0.0.1:10808
▼
┌─────────────────────────────────────────────────────────────┐
│ Xray Core (xray.exe) │
│ Inbound: SOCKS 127.0.0.1:10808 + HTTP 127.0.0.1:10809 │
│ Outbound: Trojan → WS → TLS → 127.0.0.1:40443 │
└─────────────────────────────────────────────────────────────┘
│
│ Trojan/WS/TLS 127.0.0.1:40443
▼
┌─────────────────────────────────────────────────────────────┐
│ NexNull (nexnull.exe) — SNI Spoofing Proxy │
│ Listen: 0.0.0.0:40443 │
│ Target: 104.19.229.21:443 │
│ Fake SNI: hcaptcha.com (WinDivert kernel injection) │
└─────────────────────────────────────────────────────────────┘
│
│ TCP 104.19.229.21:443
▼
┌─────────────────────────────────────────────────────────────┐
│ ISP / DPI │
│ Sees: TLS ClientHello with SNI = hcaptcha.com → ALLOW ✓ │
└─────────────────────────────────────────────────────────────┘
│
▼
Target Server (104.19.229.21)
Real TLS handshake with www.gossipglove.com
{
"log": { "loglevel": "warning" },
"inbounds": [
{ "port": 10808, "listen": "127.0.0.1", "protocol": "socks", "settings": { "udp": true } },
{ "port": 10809, "listen": "127.0.0.1", "protocol": "http" }
],
"outbounds": [{
"protocol": "trojan",
"settings": {
"servers": [{ "address": "127.0.0.1", "port": 40443, "password": "humanity" }]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": { "serverName": "www.gossipglove.com", "allowInsecure": true },
"wsSettings": { "path": "/assignment", "headers": { "Host": "www.gossipglove.com" } }
}
}]
}{
"LISTEN_HOST": "0.0.0.0",
"LISTEN_PORT": 40443,
"CONNECT_IP": "104.19.229.21",
"CONNECT_PORT": 443,
"FAKE_SNI": "hcaptcha.com",
"BROWSER_PROFILE": "random",
"TTL_SPOOF": true,
"LOG_LEVEL": "WARNING"
}- Windows 10/11 (64-bit) — must run as Administrator
- Node.js 22+
- pnpm 10+
- Python 3.11+ (for NexNull source, not needed if using compiled exe)
pnpm installpnpm run devThis starts:
- Vite dev server on
http://localhost:5173 - Electron pointing at the Vite server
- DevTools opens automatically (detached window)
Note: The full backend setup flow (download, extract, start processes) runs on every launch including dev mode. Make sure you're running as Administrator.
- All backend logs are timestamped and tagged:
[HH:MM:SS.mmm] [TAG] message - Tags:
PATHS,ADMIN,DOWNLOAD,EXTRACT,SNI,XRAY,PROXY,SETUP,IPC,VPN,CLEANUP - If
xray.exeandnexnull.exealready exist in~/Documents/PingPuff/, the download step is skipped - To force re-download: delete
~/Documents/PingPuff/xray.exeorsni/nexnull.exe - To test UI without backend: the
VpnContextfalls back to a 2.8s simulated connect ifelectronAPIis not available
pnpm run build:msiOutput: dist-electron/PingPuff-1.0.0.msi
Requires WiX Toolset v3 (electron-builder downloads it automatically to its cache on first build).
pnpm run build"win": {
"icon": "public/icon.ico",
"requestedExecutionLevel": "requireAdministrator",
"target": [{ "target": "msi", "arch": ["x64"] }]
},
"msi": {
"oneClick": false,
"perMachine": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
}perMachine: true installs to C:\Program Files\PingPuff and requires admin — consistent with the app's runtime requirement.
| Issue | Status | Notes |
|---|---|---|
reg delete ProxyServer fails if key doesn't exist |
Fixed | Wrapped in inner try/catch |
NexNull EOFError on startup |
Fixed | stdin.write('\n') bypasses the interactive prompt |
| Xray resolves on version banner before failure line | Fixed | Output buffered, failure checked before success |
| Reconnect after disconnect didn't restart processes | Fixed | vpn:connect now kills stale procs by PID + taskkill /F /IM |
| WiX 7 installed but electron-builder needs WiX 3 | Pending | electron-builder auto-downloads WiX 3 to its cache on first MSI build |
ServerPicker server list is UI-only |
By design | Servers are hardcoded; real server discovery not implemented yet |
| Ping/stats in Dashboard are simulated | By design | Real metrics from Xray not yet wired |
| Name | Role |
|---|---|
| 4m1rali | Backend & Frontend Developer |
| Matin SenPai | Backend Developer |
| TheRitalin | Network Engineer |
PingPuff — یه تلاش کوچیک برای یه نفس راحتتر