VPN Shield App
Full documentation for the VPN Shield tablet app — an encrypted proxy connection system for anonymous browsing.
Overview
The VPN app allows players to connect to encrypted proxy servers for anonymous browsing and roleplay. It supports free and premium proxies, license key activation, and provides exports for other resources to query VPN status.
Architecture
┌─────────────┐ NUI ┌──────────────┐ TriggerServer ┌──────────────┐
│ React UI │ ──────────→ │ client/vpn │ ───────────────→ │ server/vpn │
│ (VpnApp) │ │ .lua │ │ .lua │
│ │ ←────────── │ │ ←─────────────── │ │
│ proxies[] │ SendNUI │ 4 callbacks │ vpnData event │ VpnMgr.* │
│ connected │ │ │ │ │
│ keys[] │ └──────────────┘ └──────┬───────┘
└─────────────┘ │
DB.SetAppData
DB.GetAppData
DB.GetAllProxies
│
┌──────▼───────┐
│ MySQL DB │
│ │
│ wtf_vpn_ │
│ proxies │
│ wtf_vpn_ │
│ license_ │
│ keys │
│ wtf_tablet_ │
│ app_data │
└──────────────┘Files
| File | Purpose |
|---|---|
server/vpn.lua | Server logic: proxy management, license keys, connect/disconnect, exports, admin commands |
client/vpn.lua | NUI callback bridge (5 callbacks) forwarding to server events |
ui/src/apps/Vpn/index.jsx | React VPN UI: status bar, proxy lists, key activation, toasts |
config/config.lua | VPN proxy definitions, admin command settings, darkweb category config |
server/database.lua | VPN tables (wtf_vpn_proxies, wtf_vpn_license_keys) |
Data Flow
1. App Open
User clicks VPN icon
→ VpnApp mounts
→ fetchNui('vpn:getProxies')
→ client/vpn.lua → TriggerServerEvent('wtf_group:server:vpnGetProxies')
→ server/vpn.lua: VpnMgr.GetProxies(source)
├── DB.GetAllProxies() → wtf_vpn_proxies
├── DB.GetAppData(citizenid, 'vpn', 'connection') → wtf_tablet_app_data
├── DB.GetAppData(citizenid, 'vpn', 'keys') → wtf_tablet_app_data
└── Cleans up expired keys
→ TriggerClientEvent('wtf_group:client:vpnData', src, data)
→ SendNUIMessage({ action: 'vpnData', data })
→ React sets state (proxies, connected, keys)
→ If connected, view auto-sets to 'connected'2. Connect to Proxy
User clicks "Connect" on Standard VPN
→ fetchNui('vpn:connect', { proxy_id: 'standard' })
→ client/vpn.lua → TriggerServerEvent('wtf_group:server:vpnConnect', 'standard')
→ server/vpn.lua: VpnMgr.Connect(source, 'standard')
├── DB.GetProxyById('standard') → validates proxy exists
├── Checks not already connected
├── If premium: checks keys in wtf_tablet_app_data
├── DB.SetAppData(citizenid, 'vpn', 'connection', { proxy_id, proxy_name, connected, expires })
└── ox_lib:notify → success toast
→ UI reloads → shows green dot, proxy name, Disconnect button3. Disconnect
User clicks "Disconnect"
→ fetchNui('vpn:disconnect')
→ server/vpn.lua: VpnMgr.Disconnect(source)
├── DB.SetAppData(citizenid, 'vpn', 'connection', { connected: false })
└── ox_lib:notify → "Disconnected from VPN"
→ UI reloads → shows red dot, "Not Connected"4. Activate License Key
User enters "VPN-AB12-CD34-EF56-GH78" → clicks Activate
→ fetchNui('vpn:activateKey', { key_code: 'VPN-AB12-CD34-EF56-GH78' })
→ server/vpn.lua: VpnMgr.ActivateKey(source, key)
├── DB.GetLicenseKey(upperKey) → wtf_vpn_license_keys
├── Validates: key exists, used_count < max_uses, not already activated
├── Calculates expiry (permanent or duration-based)
├── DB.SetAppData(citizenid, 'vpn', 'keys', [...existing, newKey])
├── DB.UseLicenseKey(upperKey) → increments used_count
└── ox_lib:notify → "Key activated! Unlocked: Dark Web Proxy"
→ UI reloads → premium proxy shows "KEY ACTIVE" badge5. App Store Darkweb Access Check
App Store mounts
→ fetchNui('appstore:checkDarkwebAccess')
→ client/vpn.lua reads local vpnData state
→ Returns { connected: true/false, activeProxyId: 'darkweb'/nil }
→ App Store sets darkwebAccess state
→ If connected to 'darkweb' proxy:
├── 💀 Darkweb category tab appears
├── Darkweb apps (VPN, Auction) become visible
├── Install button enabled
└── Badge shows "VPN Active"
→ If not connected:
├── Darkweb category tab hidden
├── Darkweb apps filtered out
├── Install button blocked
└── Badge shows "🔒 VPN Required"Database Tables
wtf_vpn_proxies
Seeded from Config.VpnProxies on startup.
| Column | Type | Description |
|---|---|---|
id | VARCHAR(50) PK | Proxy identifier (e.g., standard, darkweb) |
name | VARCHAR(100) | Display name |
description | TEXT | Description text |
icon | VARCHAR(100) | Icon identifier |
color | VARCHAR(20) | Hex color |
category | VARCHAR(50) | Free or Premium |
is_default | TINYINT(1) | 1 = free (no key needed), 0 = premium |
sort_order | INT | Display order |
wtf_vpn_license_keys
License keys for premium proxy access.
| Column | Type | Description |
|---|---|---|
id | INT PK AUTO | Auto-increment |
key_code | VARCHAR(50) UNIQUE | Key code (e.g., VPN-AB12-CD34-EF56-GH78) |
proxy_id | VARCHAR(50) | Which proxy this key unlocks |
permanent | TINYINT(1) | 1 = never expires, 0 = duration-based |
duration | INT | Seconds until expiry (0 = permanent) |
max_uses | INT | Maximum activations |
used_count | INT | Current activations |
created_by | VARCHAR(50) | Creator identifier |
created_at | BIGINT | Creation timestamp |
wtf_tablet_app_data (shared)
VPN state stored alongside other app data.
app_id | data_key | Value |
|---|---|---|
vpn | connection | { proxy_id, proxy_name, connected, expires } |
vpn | keys | [{ key_code, proxy_id, permanent, expires }, ...] |
Proxy Configuration
Defined in config/config.lua as Config.VpnProxies:
| ID | Name | Category | Default |
|---|---|---|---|
standard | Standard VPN | Free | Yes |
regional | Regional Proxy | Free | Yes |
darkweb | Dark Web Proxy | Premium | No |
military | Military Grade | Premium | No |
quantum | Quantum Encrypted | Premium | No |
ghost | Ghost Protocol | Premium | No |
phantom | Phantom Shield | Premium | No |
Server Exports
lua
-- Check if player has ANY VPN active
exports.wtf_group:IsVpnConnected(source) → boolean
-- Check if player is connected to a SPECIFIC proxy
exports.wtf_group:IsPlayerConnectedToProxy(source, 'darkweb') → boolean
-- Get active proxy ID (or nil)
exports.wtf_group:GetPlayerActiveProxy(source) → string|nil
-- Admin: force connect player to proxy
exports.wtf_group:ConnectPlayerToProxy(source, proxyId) → boolean
-- Admin: force disconnect player
exports.wtf_group:DisconnectPlayerProxy(source) → boolean
-- Admin: generate a license key
exports.wtf_group:GenerateLicenseKey(source, proxyId, permanent, duration) → stringUsage Examples
lua
-- Dark market access check
RegisterNetEvent('darkmarket:enter', function()
local src = source
if not exports.wtf_group:IsPlayerConnectedToProxy(src, 'darkweb') then
TriggerClientEvent('ox_lib:notify', src, {
description = 'You need Dark Web VPN to access this market',
type = 'error'
})
return
end
-- Allow access
end)
-- Check any VPN status
local isProtected = exports.wtf_group:IsVpnConnected(source)
-- Get active proxy for RP logging
local proxy = exports.wtf_group:GetPlayerActiveProxy(source)
if proxy then
print('Player using proxy: ' .. proxy)
endAdmin Commands
| Command | Usage | Permission |
|---|---|---|
/givevpnkey | <serverId> <proxyId> [duration_seconds] | admin |
/vpnconnect | <serverId> <proxyId> | admin |
/vpndisconnect | <serverId> | admin |
Configuration in config/config.lua:
lua
Config.VpnAdminCommands = {
enabled = true,
ace = 'admin',
}NUI Callbacks
| Callback | Data | Description |
|---|---|---|
vpn:getProxies | — | Load all proxies + connection state + keys |
vpn:connect | { proxy_id } | Connect to a proxy |
vpn:disconnect | — | Disconnect from VPN |
vpn:activateKey | { key_code } | Activate a license key |
appstore:checkDarkwebAccess | — | Check if connected to darkweb proxy (for App Store gating) |
Client Events
| Event | Data | Description |
|---|---|---|
wtf_group:client:vpnData | { proxies, connected, activeProxyId, activeProxyName, keys } | VPN data response |
Server Events
| Event | Parameters | Description |
|---|---|---|
wtf_group:server:vpnGetProxies | — | Request VPN data |
wtf_group:server:vpnConnect | proxyId | Connect to proxy |
wtf_group:server:vpnDisconnect | — | Disconnect |
wtf_group:server:vpnActivateKey | keyCode | Activate license key |
UI Features
- Status Bar: Green/red dot showing connection status, connected proxy name, Disconnect button
- Free Proxies: List of free proxies with Connect/Disconnect buttons
- Premium Proxies: List of premium proxies with Locked/Key Active/Connect states
- License Key Input: Text input with
VPN-XXXX-XXXX-XXXX-XXXXformat, Activate button - Toast Notifications: Success (green) / Error (red) floating toasts
- SVG Icons: Custom shield, globe, skull, lock, atom, ghost, eye-off icons per proxy
- Minimize Persistence: VPN stays connected when minimized/closed; view restores correctly on reopen
- Darkweb Category: VPN and Auction apps only visible/installable in App Store when connected to
darkwebproxy
Darkweb Category
The App Store includes a special "Darkweb" category gated by VPN connection:
Config (config/config.lua):
lua
Config.DarkWeb = {
requiredProxyId = 'darkweb', -- Which proxy triggers darkweb access
apps = { 'vpn', 'auction' }, -- Apps gated by darkweb VPN
}Behavior:
- When connected to the
darkwebproxy → darkweb category tab appears, apps visible, install enabled - When not connected → category hidden, apps filtered out, install blocked
- Already-installed darkweb apps remain accessible regardless of VPN status
- Uninstall always works without VPN
App Registry (lib/appRegistry.js):
js
vpn: {
// ...
category: 'darkweb',
darkweb: true, // Marks as darkweb-gated
}
auction: {
// ...
category: 'darkweb',
darkweb: true,
}Disabling npwd_vpn
To remove the phone VPN app and use only the tablet version:
- Remove or comment out
ensure 'npwd_vpn'inserver.cfg - The
npwd_appstoreVPN functions remain (other phone resources may use them) - The tablet VPN is fully independent — no dependency on npwd_appstore
Notes
- VPN state is purely for RP purposes — no actual network proxy or IP masking
- State stored in
wtf_tablet_app_data(same table used by Notes, Browser, etc.) - License keys are single-use by default (
max_uses = 1) - Premium proxy access requires both: (1) activated key AND (2) key not expired
- Expired keys are automatically cleaned up on app open
- VPN stays connected when minimized or closed — no auto-disconnect
- When reopening VPN app, view auto-restores to connected screen if VPN is active
Config.DarkWeb.requiredProxyIdmust match a proxy ID fromConfig.VpnProxies