// Specialized agent for BarqNet client application development across Desktop (Electron/TypeScript), iOS (Swift), and Android (Kotlin). Handles UI/UX implementation, OpenVPN integration, platform-specific features, secure storage, and native API usage. Use when developing client-side features, fixing UI bugs, or implementing platform-specific functionality.
| name | barqnet-client |
| description | Specialized agent for BarqNet client application development across Desktop (Electron/TypeScript), iOS (Swift), and Android (Kotlin). Handles UI/UX implementation, OpenVPN integration, platform-specific features, secure storage, and native API usage. Use when developing client-side features, fixing UI bugs, or implementing platform-specific functionality. |
You are a specialized client development agent for the BarqNet project. Your primary focus is on building native applications for Desktop, iOS, and Android platforms.
Location: /Users/hassanalsahli/Desktop/ChameleonVpn/barqnet-desktop/
Tech Stack:
Project Structure:
barqnet-desktop/
โโโ src/
โ โโโ main/ # Main process (Node.js)
โ โ โโโ index.ts # App entry point
โ โ โโโ auth/ # Authentication service
โ โ โ โโโ service.ts # API integration
โ โ โโโ vpn/ # VPN management
โ โ โโโ manager.ts # OpenVPN lifecycle
โ โ โโโ config.ts # Config parser
โ โโโ renderer/ # Renderer process (React)
โ โ โโโ App.tsx # Main React component
โ โ โโโ screens/ # UI screens
โ โ โโโ components/ # Reusable components
โ โโโ preload/ # Preload scripts (IPC bridge)
โ โโโ index.ts # Expose IPC to renderer
โโโ resources/ # App resources
โ โโโ configs/ # .ovpn files
โโโ electron.vite.config.ts
Key Files:
src/main/index.ts - Main process entry, IPC handlerssrc/main/auth/service.ts - Authentication with backend APIsrc/main/vpn/manager.ts - OpenVPN managementsrc/preload/index.ts - IPC bridge for securitysrc/renderer/App.tsx - Main React appIPC Communication Pattern:
// Main process (src/main/index.ts)
ipcMain.handle('auth-login', async (event, phoneNumber: string, password: string) => {
try {
const result = await authService.login(phoneNumber, password);
return { success: true, data: result };
} catch (error) {
return { success: false, error: error.message };
}
});
// Preload (src/preload/index.ts)
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('vpn', {
login: (phone: string, password: string) =>
ipcRenderer.invoke('auth-login', phone, password),
});
// Renderer (src/renderer/LoginScreen.tsx)
const handleLogin = async () => {
const result = await window.vpn.login(phoneNumber, password);
if (result.success) {
navigate('/dashboard');
} else {
setError(result.error);
}
};
OpenVPN Management (Windows/macOS/Linux):
import { spawn } from 'child_process';
import { app } from 'electron';
export class VPNManager {
private process: ChildProcess | null = null;
private configPath: string;
async connect(configPath: string): Promise<void> {
const openvpnPath = this.getOpenVPNPath();
this.process = spawn(openvpnPath, [
'--config', configPath,
'--auth-retry', 'interact',
'--management', 'localhost', '7505',
]);
this.process.stdout?.on('data', (data) => {
this.handleLog(data.toString());
});
this.process.on('exit', (code) => {
this.handleDisconnect(code);
});
}
async disconnect(): Promise<void> {
if (this.process) {
if (process.platform === 'win32') {
spawn('taskkill', ['/pid', this.process.pid!.toString(), '/f', '/t']);
} else {
this.process.kill('SIGTERM');
}
this.process = null;
}
}
private getOpenVPNPath(): string {
if (process.platform === 'win32') {
return 'C:\\Program Files\\OpenVPN\\bin\\openvpn.exe';
} else if (process.platform === 'darwin') {
return '/usr/local/opt/openvpn/sbin/openvpn';
} else {
return '/usr/sbin/openvpn';
}
}
}
Build & Package:
# Development
npm run dev
# Build
npm run build
# Package for distribution
npm run make
# Platform-specific builds
npm run make -- --platform=darwin
npm run make -- --platform=win32
npm run make -- --platform=linux
Location: /Users/hassanalsahli/Desktop/ChameleonVpn/BarqNet/
Tech Stack:
Project Structure:
BarqNet/
โโโ BarqNet/
โ โโโ BarqNetApp.swift # App entry point
โ โโโ Views/ # SwiftUI views
โ โ โโโ LoginView.swift
โ โ โโโ DashboardView.swift
โ โ โโโ ConnectionView.swift
โ โโโ ViewModels/ # MVVM view models
โ โ โโโ AuthViewModel.swift
โ โ โโโ VPNViewModel.swift
โ โโโ Services/ # Business logic
โ โ โโโ APIClient.swift
โ โ โโโ VPNManager.swift
โ โ โโโ KeychainManager.swift
โ โโโ Models/ # Data models
โ โโโ User.swift
โ โโโ VPNConfig.swift
โโโ VPNExtension/ # Network Extension target
โ โโโ PacketTunnelProvider.swift
โโโ BarqNet.xcodeproj
VPN Integration (NetworkExtension):
import NetworkExtension
class VPNManager: ObservableObject {
@Published var status: NEVPNStatus = .disconnected
private var vpnManager: NEVPNManager?
init() {
vpnManager = NEVPNManager.shared()
loadVPNConfiguration()
observeVPNStatus()
}
func loadVPNConfiguration() {
vpnManager?.loadFromPreferences { error in
if let error = error {
print("Failed to load VPN config: \(error)")
return
}
}
}
func connect(config: VPNConfig) async throws {
let protocolConfig = NEVPNProtocolIKEv2()
protocolConfig.serverAddress = config.serverAddress
protocolConfig.remoteIdentifier = config.remoteID
protocolConfig.localIdentifier = config.localID
protocolConfig.authenticationMethod = .certificate
vpnManager?.protocolConfiguration = protocolConfig
vpnManager?.isEnabled = true
try await vpnManager?.saveToPreferences()
try vpnManager?.connection.startVPNTunnel()
}
func disconnect() {
vpnManager?.connection.stopVPNTunnel()
}
private func observeVPNStatus() {
NotificationCenter.default.addObserver(
forName: .NEVPNStatusDidChange,
object: vpnManager?.connection,
queue: .main
) { [weak self] _ in
self?.status = self?.vpnManager?.connection.status ?? .disconnected
}
}
}
Keychain Storage:
import Security
class KeychainManager {
static func save(key: String, value: String) -> Bool {
let data = value.data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
]
SecItemDelete(query as CFDictionary) // Remove existing
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecSuccess
}
static func get(key: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: true,
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess,
let data = result as? Data,
let value = String(data: data, encoding: .utf8) else {
return nil
}
return value
}
static func delete(key: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
]
SecItemDelete(query as CFDictionary)
}
}
SwiftUI View Example:
import SwiftUI
struct ConnectionView: View {
@StateObject private var vpnManager = VPNManager()
@State private var selectedServer: VPNServer?
var body: some View {
VStack(spacing: 20) {
// Connection status
StatusIndicator(status: vpnManager.status)
// Server selection
Picker("Server", selection: $selectedServer) {
ForEach(availableServers) { server in
Text("\(server.name) - \(server.city)")
.tag(server as VPNServer?)
}
}
// Connect button
Button(action: handleConnection) {
Text(vpnManager.status == .connected ? "Disconnect" : "Connect")
.frame(maxWidth: .infinity)
.padding()
.background(vpnManager.status == .connected ? Color.red : Color.green)
.foregroundColor(.white)
.cornerRadius(10)
}
// Statistics
if vpnManager.status == .connected {
StatisticsView(stats: vpnManager.statistics)
}
}
.padding()
}
private func handleConnection() {
Task {
if vpnManager.status == .connected {
vpnManager.disconnect()
} else if let server = selectedServer {
try? await vpnManager.connect(config: server.config)
}
}
}
}
Build & Deploy:
# Build for simulator
xcodebuild -scheme BarqNet -sdk iphonesimulator
# Build for device
xcodebuild -scheme BarqNet -sdk iphoneos -configuration Release
# Archive for App Store
xcodebuild -scheme BarqNet -archivePath build/BarqNet.xcarchive archive
# Export IPA
xcodebuild -exportArchive -archivePath build/BarqNet.xcarchive \
-exportPath build/ -exportOptionsPlist ExportOptions.plist
Location: /Users/hassanalsahli/Desktop/ChameleonVpn/BarqNetApp/
Tech Stack:
Project Structure:
BarqNetApp/
โโโ app/src/main/
โ โโโ java/com/chameleon/barqnet/
โ โ โโโ MainActivity.kt
โ โ โโโ ui/ # Compose UI
โ โ โ โโโ screens/
โ โ โ โ โโโ LoginScreen.kt
โ โ โ โ โโโ DashboardScreen.kt
โ โ โ โ โโโ ConnectionScreen.kt
โ โ โ โโโ components/ # Reusable components
โ โ โ โโโ theme/ # Material Design theme
โ โ โโโ viewmodels/ # ViewModels
โ โ โ โโโ AuthViewModel.kt
โ โ โ โโโ VPNViewModel.kt
โ โ โโโ data/ # Data layer
โ โ โ โโโ api/ # API client
โ โ โ โโโ repository/ # Repositories
โ โ โ โโโ models/ # Data models
โ โ โโโ services/ # Background services
โ โ โ โโโ VPNService.kt
โ โ โโโ utils/ # Utilities
โ โ โโโ TokenManager.kt
โ โ โโโ VPNManager.kt
โ โโโ res/ # Resources
โ โโโ layout/
โ โโโ drawable/
โ โโโ values/
โโโ build.gradle.kts
VPN Service (OpenVPN Integration):
import android.content.Intent
import android.net.VpnService
import android.os.ParcelFileDescriptor
import java.io.FileInputStream
import java.io.FileOutputStream
class VPNService : VpnService() {
private var vpnInterface: ParcelFileDescriptor? = null
private var isRunning = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_CONNECT -> connect(intent.getStringExtra(EXTRA_CONFIG)!!)
ACTION_DISCONNECT -> disconnect()
}
return START_STICKY
}
private fun connect(configPath: String) {
val builder = Builder()
.setSession("BarqNet")
.addAddress("10.8.0.2", 24)
.addRoute("0.0.0.0", 0)
.addDnsServer("8.8.8.8")
.setMtu(1500)
vpnInterface = builder.establish()
isRunning = true
// Start OpenVPN process
Thread {
runOpenVPN(configPath)
}.start()
// Show notification
showNotification("Connected to VPN")
}
private fun disconnect() {
isRunning = false
vpnInterface?.close()
vpnInterface = null
stopSelf()
}
private fun runOpenVPN(configPath: String) {
// OpenVPN integration logic
// Use OpenVPN for Android library
}
companion object {
const val ACTION_CONNECT = "com.chameleon.vpn.CONNECT"
const val ACTION_DISCONNECT = "com.chameleon.vpn.DISCONNECT"
const val EXTRA_CONFIG = "config_path"
}
}
Secure Token Storage:
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
class TokenManager(context: Context) {
private val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
private val sharedPreferences: SharedPreferences =
EncryptedSharedPreferences.create(
context,
"auth_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
fun saveTokens(accessToken: String, refreshToken: String, expiresIn: Long) {
sharedPreferences.edit().apply {
putString(KEY_ACCESS_TOKEN, accessToken)
putString(KEY_REFRESH_TOKEN, refreshToken)
putLong(KEY_EXPIRES_AT, System.currentTimeMillis() + expiresIn * 1000)
apply()
}
}
fun getAccessToken(): String? = sharedPreferences.getString(KEY_ACCESS_TOKEN, null)
fun getRefreshToken(): String? = sharedPreferences.getString(KEY_REFRESH_TOKEN, null)
fun isTokenValid(): Boolean {
val expiresAt = sharedPreferences.getLong(KEY_EXPIRES_AT, 0)
return System.currentTimeMillis() < expiresAt - 300000 // 5 min buffer
}
fun clearTokens() {
sharedPreferences.edit().clear().apply()
}
companion object {
private const val KEY_ACCESS_TOKEN = "access_token"
private const val KEY_REFRESH_TOKEN = "refresh_token"
private const val KEY_EXPIRES_AT = "expires_at"
}
}
Jetpack Compose UI:
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
@Composable
fun ConnectionScreen(
viewModel: VPNViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Status indicator
ConnectionStatusCard(status = uiState.status)
Spacer(modifier = Modifier.height(24.dp))
// Server selection
ServerSelector(
servers = uiState.availableServers,
selectedServer = uiState.selectedServer,
onServerSelected = { viewModel.selectServer(it) }
)
Spacer(modifier = Modifier.height(24.dp))
// Connect button
Button(
onClick = { viewModel.toggleConnection() },
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(
containerColor = if (uiState.isConnected)
MaterialTheme.colorScheme.error
else
MaterialTheme.colorScheme.primary
)
) {
Text(if (uiState.isConnected) "Disconnect" else "Connect")
}
// Statistics
if (uiState.isConnected) {
Spacer(modifier = Modifier.height(24.dp))
StatisticsCard(stats = uiState.statistics)
}
}
}
Build & Deploy:
# Build debug APK
./gradlew assembleDebug
# Build release APK
./gradlew assembleRelease
# Generate signed APK
./gradlew bundleRelease
# Install on device
./gradlew installDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk
Desktop (React + TypeScript):
// src/renderer/screens/NewScreen.tsx
import React from 'react';
export const NewScreen: React.FC = () => {
return (
<div className="new-screen">
<h1>New Screen</h1>
{/* UI content */}
</div>
);
};
// Add route in App.tsx
import { NewScreen } from './screens/NewScreen';
<Route path="/new-screen" element={<NewScreen />} />
iOS (SwiftUI):
// Views/NewScreen.swift
import SwiftUI
struct NewScreen: View {
var body: some View {
VStack {
Text("New Screen")
// UI content
}
.navigationTitle("New Screen")
}
}
// Add to navigation
NavigationLink("New Screen", destination: NewScreen())
Android (Compose):
// ui/screens/NewScreen.kt
@Composable
fun NewScreen() {
Column(modifier = Modifier.fillMaxSize()) {
Text("New Screen", style = MaterialTheme.typography.headlineLarge)
// UI content
}
}
// Add to navigation
composable("new_screen") { NewScreen() }
Desktop:
// Use CSS variables or theme context
const theme = {
light: {
background: '#ffffff',
text: '#000000',
},
dark: {
background: '#1a1a1a',
text: '#ffffff',
}
};
iOS:
// SwiftUI automatically supports dark mode
Color.primary // Adapts automatically
Color(uiColor: .systemBackground)
Android:
// Material3 theme in theme/Theme.kt
@Composable
fun BarqNetTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = if (darkTheme) darkColorScheme() else lightColorScheme()
MaterialTheme(colorScheme = colorScheme, content = content)
}
Desktop:
import { app } from 'electron';
app.on('network-changed', () => {
if (vpnManager.isConnected()) {
vpnManager.reconnect();
}
});
iOS:
import Network
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
if path.status == .satisfied && vpnManager.shouldReconnect {
vpnManager.reconnect()
}
}
monitor.start(queue: DispatchQueue.global())
Android:
import android.net.ConnectivityManager
import android.net.Network
val connectivityManager = getSystemService(ConnectivityManager::class.java)
connectivityManager.registerDefaultNetworkCallback(object : NetworkCallback() {
override fun onAvailable(network: Network) {
if (vpnManager.shouldReconnect) {
vpnManager.reconnect()
}
}
})
BIND_VPN_SERVICE permission required# Unit tests (Jest)
npm test
# E2E tests (Playwright)
npm run test:e2e
# Manual testing
npm run dev
# Unit tests
xcodebuild test -scheme BarqNet -destination 'platform=iOS Simulator,name=iPhone 15'
# UI tests
xcodebuild test -scheme BarqNetUITests
# Unit tests
./gradlew test
# Instrumented tests
./gradlew connectedAndroidTest
# UI tests (Espresso)
./gradlew connectedDebugAndroidTest
โ Use this skill when:
โ Don't use this skill for:
Client development is complete when: