1269 lines
46 KiB
TypeScript
1269 lines
46 KiB
TypeScript
import express from 'express';
|
|
import { createServer as createViteServer } from 'vite';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import dotenv from 'dotenv';
|
|
import {
|
|
AndroidVM,
|
|
UserSubscription,
|
|
AndroidVersion,
|
|
Architecture,
|
|
BootloaderConfig,
|
|
DeviceProfileId,
|
|
ProxyConfig,
|
|
ProxyType,
|
|
} from './src/types.js';
|
|
import { HardwareProfiles, DEFAULT_ANDROID_APPS } from './src/data/deviceProfiles.js';
|
|
|
|
dotenv.config();
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const app = express();
|
|
const PORT = 3000;
|
|
|
|
app.use(express.json({ limit: '50mb' }));
|
|
|
|
// In-Memory Database for Hypervisor & Rental Subscriptions
|
|
let userSubscription: UserSubscription = {
|
|
plan: 'free',
|
|
maxVms: 1, // 1 Free VM instance
|
|
pricePerMonth: 0,
|
|
activeVmCount: 1,
|
|
renewsAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
paymentMethod: 'Free Tier Active',
|
|
};
|
|
|
|
let virtualMachines: AndroidVM[] = [
|
|
{
|
|
id: 'vm-cloud-001',
|
|
name: 'Pixel 8 Pro - Android 14 (AOSP)',
|
|
androidVersion: '14 (UpsideDownCake)',
|
|
arch: 'x86_64',
|
|
profile: HardwareProfiles.PIXEL_8_PRO,
|
|
ramGb: 8,
|
|
vcpu: 4,
|
|
storageGb: 64,
|
|
bootloader: {
|
|
isUnlocked: true,
|
|
rootMethod: 'magisk',
|
|
customRecovery: true,
|
|
selinuxMode: 'permissive',
|
|
kernelCmdline: 'console=ttyMSM0,115200 androidboot.hardware=qcom androidboot.selinux=permissive',
|
|
gpuAcceleration: 'swiftshader',
|
|
},
|
|
status: 'running',
|
|
ipAddress: '192.168.122.105',
|
|
adbPort: 5555,
|
|
vncPort: 5901,
|
|
batteryLevel: 94,
|
|
isCharging: true,
|
|
orientation: 'portrait',
|
|
location: {
|
|
latitude: 37.7749,
|
|
longitude: -122.4194,
|
|
addressName: 'San Francisco, CA, USA',
|
|
},
|
|
metrics: {
|
|
cpuUsagePct: 18.5,
|
|
ramUsageMb: 3420,
|
|
totalRamMb: 8192,
|
|
fps: 60,
|
|
networkRxKbps: 450,
|
|
networkTxKbps: 120,
|
|
uptimeSeconds: 14200,
|
|
},
|
|
installedApps: [...DEFAULT_ANDROID_APPS],
|
|
currentAppPackage: 'com.android.launcher3',
|
|
proxy: {
|
|
id: 'px-1',
|
|
enabled: true,
|
|
type: 'SOCKS5',
|
|
host: '104.28.19.42',
|
|
port: 1080,
|
|
country: 'United States',
|
|
countryCode: 'US',
|
|
anonymity: 'Elite',
|
|
latencyMs: 38,
|
|
externalIp: '104.28.19.42',
|
|
},
|
|
createdAt: new Date(Date.now() - 4 * 3600 * 1000).toISOString(),
|
|
snapshotCount: 2,
|
|
},
|
|
];
|
|
|
|
// Proxifly Master Proxy Pool Dataset
|
|
const PROXIFLY_PROXIES: ProxyConfig[] = [
|
|
{ id: 'px-1', enabled: true, type: 'SOCKS5', host: '104.28.19.42', port: 1080, country: 'United States', countryCode: 'US', anonymity: 'Elite', latencyMs: 38, externalIp: '104.28.19.42' },
|
|
{ id: 'px-2', enabled: true, type: 'HTTP', host: '185.220.101.5', port: 8080, country: 'Germany', countryCode: 'DE', anonymity: 'Elite', latencyMs: 62, externalIp: '185.220.101.5' },
|
|
{ id: 'px-3', enabled: true, type: 'SOCKS5', host: '139.162.24.11', port: 1080, country: 'Japan', countryCode: 'JP', anonymity: 'Elite', latencyMs: 110, externalIp: '139.162.24.11' },
|
|
{ id: 'px-4', enabled: true, type: 'HTTPS', host: '51.15.22.180', port: 3128, country: 'France', countryCode: 'FR', anonymity: 'Anonymous', latencyMs: 75, externalIp: '51.15.22.180' },
|
|
{ id: 'px-5', enabled: true, type: 'SOCKS5', host: '128.199.201.89', port: 1080, country: 'Singapore', countryCode: 'SG', anonymity: 'Elite', latencyMs: 145, externalIp: '128.199.201.89' },
|
|
{ id: 'px-6', enabled: true, type: 'HTTP', host: '198.244.180.20', port: 8000, country: 'United Kingdom', countryCode: 'GB', anonymity: 'Elite', latencyMs: 54, externalIp: '198.244.180.20' },
|
|
{ id: 'px-7', enabled: true, type: 'SOCKS5', host: '159.203.42.109', port: 1080, country: 'Canada', countryCode: 'CA', anonymity: 'Elite', latencyMs: 42, externalIp: '159.203.42.109' },
|
|
{ id: 'px-8', enabled: true, type: 'HTTP', host: '177.71.180.4', port: 8080, country: 'Brazil', countryCode: 'BR', anonymity: 'Anonymous', latencyMs: 168, externalIp: '177.71.180.4' },
|
|
{ id: 'px-9', enabled: true, type: 'SOCKS5', host: '103.214.112.50', port: 1080, country: 'India', countryCode: 'IN', anonymity: 'Elite', latencyMs: 195, externalIp: '103.214.112.50' },
|
|
{ id: 'px-10', enabled: true, type: 'HTTPS', host: '45.76.182.90', port: 8443, country: 'Australia', countryCode: 'AU', anonymity: 'Elite', latencyMs: 180, externalIp: '45.76.182.90' },
|
|
{ id: 'px-11', enabled: true, type: 'SOCKS5', host: '211.233.72.18', port: 1080, country: 'South Korea', countryCode: 'KR', anonymity: 'Elite', latencyMs: 128, externalIp: '211.233.72.18' },
|
|
{ id: 'px-12', enabled: true, type: 'HTTP', host: '94.23.148.10', port: 3128, country: 'Netherlands', countryCode: 'NL', anonymity: 'Elite', latencyMs: 49, externalIp: '94.23.148.10' },
|
|
];
|
|
|
|
// Synchronize Subscription Count
|
|
function syncActiveVmCount() {
|
|
userSubscription.activeVmCount = virtualMachines.length;
|
|
}
|
|
|
|
// Background simulation for VM metrics (CPU, RAM, FPS updates)
|
|
setInterval(() => {
|
|
virtualMachines.forEach((vm) => {
|
|
if (vm.status === 'running') {
|
|
vm.metrics.cpuUsagePct = Math.round((10 + Math.random() * 35) * 10) / 10;
|
|
vm.metrics.fps = Math.round(55 + Math.random() * 5);
|
|
vm.metrics.networkRxKbps = Math.round(100 + Math.random() * 800);
|
|
vm.metrics.networkTxKbps = Math.round(20 + Math.random() * 200);
|
|
vm.metrics.uptimeSeconds += 2;
|
|
}
|
|
});
|
|
}, 2000);
|
|
|
|
// ================= API ENDPOINTS ================= //
|
|
|
|
// 1. Health check
|
|
app.get('/api/health', (req, res) => {
|
|
res.json({
|
|
status: 'ok',
|
|
kvmAvailable: true,
|
|
hypervisorVersion: 'KVM/QEMU 8.2.0 (VirtIO-GPU Accelerated)',
|
|
vmsActive: virtualMachines.length,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
});
|
|
|
|
// 2. Get User Rental Subscription Status
|
|
app.get('/api/user/subscription', (req, res) => {
|
|
syncActiveVmCount();
|
|
res.json(userSubscription);
|
|
});
|
|
|
|
// 3. Upgrade or Switch Subscription Tier ($5/mo for 5 VMs)
|
|
app.post('/api/user/subscription/upgrade', (req, res) => {
|
|
const { targetPlan, paymentMethod } = req.body;
|
|
|
|
if (targetPlan === 'pro') {
|
|
userSubscription = {
|
|
plan: 'pro',
|
|
maxVms: 5, // 5 VMs for $5/mo
|
|
pricePerMonth: 5,
|
|
activeVmCount: virtualMachines.length,
|
|
renewsAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
paymentMethod: paymentMethod || 'Visa ending in 4242',
|
|
};
|
|
} else {
|
|
userSubscription = {
|
|
plan: 'free',
|
|
maxVms: 1, // 1 Free VM
|
|
pricePerMonth: 0,
|
|
activeVmCount: virtualMachines.length,
|
|
renewsAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
paymentMethod: 'Free Tier Active',
|
|
};
|
|
}
|
|
|
|
res.json({
|
|
message: `Successfully updated subscription to ${userSubscription.plan.toUpperCase()} tier!`,
|
|
subscription: userSubscription,
|
|
});
|
|
});
|
|
|
|
// 4. List All Virtual Machines
|
|
app.get('/api/vms', (req, res) => {
|
|
res.json(virtualMachines);
|
|
});
|
|
|
|
// 5. Create / Deploy New Android Virtual Device
|
|
app.post('/api/vms/create', (req, res) => {
|
|
syncActiveVmCount();
|
|
|
|
// Enforce Rental Tier Limit
|
|
if (virtualMachines.length >= userSubscription.maxVms) {
|
|
return res.status(403).json({
|
|
error: 'SUBSCRIPTION_LIMIT_EXCEEDED',
|
|
message: `Your current ${userSubscription.plan.toUpperCase()} plan limit is ${userSubscription.maxVms} Android VM(s). Upgrade to Pro ($5/mo for 5 Android VMs) to deploy more instances!`,
|
|
currentPlan: userSubscription.plan,
|
|
maxVms: userSubscription.maxVms,
|
|
});
|
|
}
|
|
|
|
const {
|
|
name,
|
|
androidVersion = '14 (UpsideDownCake)',
|
|
arch = 'x86_64',
|
|
profileId = 'pixel-8-pro',
|
|
ramGb = 8,
|
|
vcpu = 4,
|
|
storageGb = 64,
|
|
bootloaderUnlocked = true,
|
|
rootMethod = 'magisk',
|
|
selinuxMode = 'permissive',
|
|
gpuAcceleration = 'swiftshader',
|
|
} = req.body;
|
|
|
|
let selectedProfile = HardwareProfiles.PIXEL_8_PRO;
|
|
if (profileId === 'galaxy-s24-ultra') selectedProfile = HardwareProfiles.GALAXY_S24_ULTRA;
|
|
if (profileId === 'oneplus-12') selectedProfile = HardwareProfiles.ONEPLUS_12;
|
|
if (profileId === 'aosp-tablet-10') selectedProfile = HardwareProfiles.AOSP_TABLET;
|
|
|
|
const newVmId = `vm-cloud-00${virtualMachines.length + 1}`;
|
|
const assignedAdbPort = 5555 + virtualMachines.length;
|
|
const assignedVncPort = 5901 + virtualMachines.length;
|
|
|
|
const newVm: AndroidVM = {
|
|
id: newVmId,
|
|
name: name || `${selectedProfile.name} - Android ${androidVersion.split(' ')[0]}`,
|
|
androidVersion: androidVersion as AndroidVersion,
|
|
arch: arch as Architecture,
|
|
profile: selectedProfile,
|
|
ramGb: Number(ramGb),
|
|
vcpu: Number(vcpu),
|
|
storageGb: Number(storageGb),
|
|
bootloader: {
|
|
isUnlocked: Boolean(bootloaderUnlocked),
|
|
rootMethod: rootMethod,
|
|
customRecovery: true,
|
|
selinuxMode: selinuxMode,
|
|
kernelCmdline: `console=ttyMSM0,115200 androidboot.hardware=qcom androidboot.selinux=${selinuxMode}`,
|
|
gpuAcceleration: gpuAcceleration,
|
|
},
|
|
status: 'running',
|
|
ipAddress: `192.168.122.${105 + virtualMachines.length}`,
|
|
adbPort: assignedAdbPort,
|
|
vncPort: assignedVncPort,
|
|
batteryLevel: 100,
|
|
isCharging: true,
|
|
orientation: 'portrait',
|
|
location: {
|
|
latitude: 37.7749,
|
|
longitude: -122.4194,
|
|
addressName: 'San Francisco, CA, USA',
|
|
},
|
|
metrics: {
|
|
cpuUsagePct: 12.0,
|
|
ramUsageMb: Math.round(Number(ramGb) * 1024 * 0.35),
|
|
totalRamMb: Number(ramGb) * 1024,
|
|
fps: 60,
|
|
networkRxKbps: 320,
|
|
networkTxKbps: 80,
|
|
uptimeSeconds: 10,
|
|
},
|
|
installedApps: [...DEFAULT_ANDROID_APPS],
|
|
currentAppPackage: 'com.android.launcher3',
|
|
createdAt: new Date().toISOString(),
|
|
snapshotCount: 1,
|
|
};
|
|
|
|
virtualMachines.push(newVm);
|
|
syncActiveVmCount();
|
|
|
|
res.status(201).json(newVm);
|
|
});
|
|
|
|
// 6. Delete / Terminate Android VM Instance
|
|
app.delete('/api/vms/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
const initialLen = virtualMachines.length;
|
|
virtualMachines = virtualMachines.filter((vm) => vm.id !== id);
|
|
|
|
if (virtualMachines.length === initialLen) {
|
|
return res.status(404).json({ error: 'VM instance not found' });
|
|
}
|
|
|
|
syncActiveVmCount();
|
|
res.json({ message: `Terminated VM instance ${id}`, activeVms: virtualMachines.length });
|
|
});
|
|
|
|
// 7. Power Actions (Start, Reboot, Stop, Fastboot, Recovery, Reset)
|
|
app.post('/api/vms/:id/power', (req, res) => {
|
|
const { id } = req.params;
|
|
const { action } = req.body;
|
|
|
|
const vm = virtualMachines.find((v) => v.id === id);
|
|
if (!vm) {
|
|
return res.status(404).json({ error: 'VM not found' });
|
|
}
|
|
|
|
if (action === 'start') {
|
|
vm.status = 'running';
|
|
vm.metrics.uptimeSeconds = 0;
|
|
} else if (action === 'stop') {
|
|
vm.status = 'stopped';
|
|
} else if (action === 'reboot') {
|
|
vm.status = 'booting';
|
|
setTimeout(() => {
|
|
vm.status = 'running';
|
|
vm.metrics.uptimeSeconds = 0;
|
|
}, 2000);
|
|
} else if (action === 'boot_fastboot') {
|
|
vm.status = 'fastboot';
|
|
} else if (action === 'boot_recovery') {
|
|
vm.status = 'recovery';
|
|
} else if (action === 'reset') {
|
|
vm.installedApps = [...DEFAULT_ANDROID_APPS];
|
|
vm.currentAppPackage = 'com.android.launcher3';
|
|
vm.status = 'running';
|
|
}
|
|
|
|
res.json({ message: `Executed power action ${action} on ${vm.name}`, vm });
|
|
});
|
|
|
|
// 8. Execute ADB Shell Commands
|
|
app.post('/api/vms/:id/adb', (req, res) => {
|
|
const { id } = req.params;
|
|
const { command } = req.body;
|
|
|
|
const vm = virtualMachines.find((v) => v.id === id);
|
|
if (!vm) return res.status(404).json({ error: 'VM not found' });
|
|
|
|
const cmd = (command || '').trim();
|
|
let output = '';
|
|
|
|
if (cmd.includes('settings get global http_proxy')) {
|
|
output = vm.proxy && vm.proxy.enabled ? `${vm.proxy.host}:${vm.proxy.port}` : ':0';
|
|
} else if (cmd.includes('settings put global http_proxy')) {
|
|
const val = cmd.split('http_proxy')[1]?.trim() || '';
|
|
if (val === ':0' || val === 'none' || val === '""') {
|
|
if (vm.proxy) vm.proxy.enabled = false;
|
|
output = `Global HTTP proxy reset to direct WAN.`;
|
|
} else {
|
|
const [host, portStr] = val.split(':');
|
|
const port = parseInt(portStr || '8080');
|
|
vm.proxy = {
|
|
enabled: true,
|
|
type: 'HTTP',
|
|
host: host || '127.0.0.1',
|
|
port,
|
|
country: 'Configured Proxy',
|
|
countryCode: 'US',
|
|
externalIp: host,
|
|
latencyMs: 45,
|
|
};
|
|
output = `Global HTTP proxy set to ${host}:${port}`;
|
|
}
|
|
} else if (cmd.includes('curl ifconfig.me') || cmd.includes('myip') || cmd.includes('ip route')) {
|
|
if (vm.proxy && vm.proxy.enabled) {
|
|
output = `[Proxy Route Active via ${vm.proxy.type}]\nExit IP: ${vm.proxy.externalIp || vm.proxy.host}\nHost Port: ${vm.proxy.host}:${vm.proxy.port}\nLocation: ${vm.proxy.country || 'Global Proxy'} (${vm.proxy.countryCode || 'US'})\nAnonymity Level: ${vm.proxy.anonymity || 'Elite'}`;
|
|
} else {
|
|
output = `[Direct WAN Connection]\nPublic IP: ${vm.ipAddress}\nProxy: Disabled (Direct Connection)`;
|
|
}
|
|
} else if (cmd.startsWith('adb shell getprop') || cmd === 'getprop') {
|
|
output = `[ro.build.version.release]: [${vm.androidVersion.split(' ')[0]}]
|
|
[ro.build.version.sdk]: [34]
|
|
[ro.product.model]: [${vm.profile.name}]
|
|
[ro.product.brand]: [${vm.profile.manufacturer}]
|
|
[ro.boot.flash.locked]: [${vm.bootloader.isUnlocked ? '0' : '1'}]
|
|
[ro.boot.selinux]: [${vm.bootloader.selinuxMode}]
|
|
[ro.hardware]: [qcom]
|
|
[ro.sf.lcd_density]: [${vm.profile.densityDpi}]
|
|
[net.dns1]: [8.8.8.8]`;
|
|
} else if (cmd.includes('input tap')) {
|
|
const parts = cmd.split('input tap')[1]?.trim().split(/\s+/) || ['0', '0'];
|
|
const x = parts[0] || '100';
|
|
const y = parts[1] || '200';
|
|
output = `[Input Event] Simulated touch tap at (${x}, ${y}) on device screen display_id=0`;
|
|
} else if (cmd.includes('input text')) {
|
|
const text = cmd.split('input text')[1]?.trim() || '';
|
|
output = `[Input Event] Injected IME text string: "${text.replace(/^['"]|['"]$/g, '')}"`;
|
|
} else if (cmd.includes('input keyevent')) {
|
|
const key = cmd.split('input keyevent')[1]?.trim() || 'HOME';
|
|
output = `[Input Event] Injected hardware keyevent: ${key}`;
|
|
} else if (cmd.includes('input swipe')) {
|
|
output = `[Input Event] Simulated touch drag gesture ${cmd}`;
|
|
} else if (cmd.includes('am start')) {
|
|
const pkgMatch = cmd.match(/(com\.[a-zA-Z0-0_.]+)/);
|
|
const targetPkg = pkgMatch ? pkgMatch[1] : 'com.android.launcher3';
|
|
vm.currentAppPackage = targetPkg;
|
|
output = `Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] pkg=${targetPkg} }\nStatus: Activity launched successfully on VM display #0`;
|
|
} else if (cmd.includes('monkey')) {
|
|
const pkgMatch = cmd.match(/-p\s+(com\.[a-zA-Z0-0_.]+)/);
|
|
const targetPkg = pkgMatch ? pkgMatch[1] : vm.currentAppPackage;
|
|
output = `:Monkey: seed=1723328200 count=100
|
|
:AllowPackage: ${targetPkg}
|
|
:IncludeCategory: android.intent.category.LAUNCHER
|
|
// Event injected: 100 pseudo-random touch/drag/key gestures
|
|
// Activity: ${targetPkg} handled 100/100 events with 0 crashes or ANRs.
|
|
Events injected: 100
|
|
System appear to be: aOK (animator stops, top activity=${targetPkg})`;
|
|
} else if (cmd.startsWith('adb install') || cmd.includes('pm install')) {
|
|
const apkName = cmd.split('/').pop()?.replace('.apk', '') || 'CustomApp';
|
|
const pkgName = `com.custom.${apkName.toLowerCase().replace(/[^a-z0-0]/g, '')}`;
|
|
const exists = vm.installedApps.some((a) => a.packageName === pkgName);
|
|
if (!exists) {
|
|
vm.installedApps.push({
|
|
packageName: pkgName,
|
|
appName: apkName.toUpperCase(),
|
|
version: '1.0.0-release',
|
|
iconName: 'Smartphone',
|
|
isSystem: false,
|
|
sizeMb: 24.5,
|
|
});
|
|
}
|
|
output = `Performing Streamed Install\nSuccess: Package ${pkgName} installed on system storage`;
|
|
} else if (cmd.includes('pm list packages') || cmd === 'pm list') {
|
|
output = vm.installedApps.map((a) => `package:${a.packageName}`).join('\n');
|
|
} else if (cmd.startsWith('su')) {
|
|
if (vm.bootloader.rootMethod !== 'none') {
|
|
output = `# root@${vm.profile.id}:/# id\nuid=0(root) gid=0(root) groups=0(root) context=u:r:su:s0\nRoot privilege granted via ${vm.bootloader.rootMethod.toUpperCase()}`;
|
|
} else {
|
|
output = `Permission denied: Root privilege unavailable (Bootloader Root is NONE)`;
|
|
}
|
|
} else if (cmd.includes('cat /proc/cpuinfo')) {
|
|
output = `Processor\t: ARMv8 Processor rev 4 (v8l)
|
|
processor\t: 0..${vm.vcpu - 1}
|
|
BogoMIPS\t: 38.40
|
|
Features\t: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
|
|
CPU implementer\t: 0x51
|
|
CPU architecture: 8
|
|
CPU variant\t: 0x2
|
|
CPU part\t: 0x805
|
|
CPU revision\t: 1
|
|
Hardware\t: Hypervisor VirtIO QEMU`;
|
|
} else if (cmd.includes('dumpsys battery')) {
|
|
output = `Current Battery Service state:
|
|
AC powered: false
|
|
USB powered: ${vm.isCharging}
|
|
Wireless powered: false
|
|
level: ${vm.batteryLevel}
|
|
scale: 100
|
|
voltage: 4210
|
|
temperature: 284
|
|
technology: Li-ion`;
|
|
} else if (cmd.includes('dumpsys activity')) {
|
|
output = `ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
|
|
Display #0 (activities from top to bottom):
|
|
Stack #1: type=standard mode=fullscreen
|
|
Task id #12
|
|
* ActivityRecord{b4a12c u0 ${vm.currentAppPackage} t12}
|
|
running=true visible=true task=Task{b4a12c #12}`;
|
|
} else if (cmd.includes('logcat')) {
|
|
output = `08-10 18:00:12.102 1024 1024 I ActivityTaskManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] pkg=${vm.currentAppPackage}}
|
|
08-10 18:00:12.145 1024 1240 D SurfaceFlinger: Layer ${vm.currentAppPackage} rendered at 60.0 FPS
|
|
08-10 18:00:12.210 512 512 I Magisk : [SU] Shell (uid: 2000) granted superuser permissions`;
|
|
} else {
|
|
output = `[adb shell @ ${vm.ipAddress}:${vm.adbPort}]\n$ ${cmd}\nCommand executed successfully with status 0.`;
|
|
}
|
|
|
|
res.json({ output, exitCode: 0, executionTimeMs: 38 });
|
|
});
|
|
|
|
// 8b. Execute Programmatic Automation Script Endpoint
|
|
app.post('/api/vms/:id/automation/run', (req, res) => {
|
|
const { id } = req.params;
|
|
const { steps } = req.body; // array of step objects or command strings
|
|
|
|
const vm = virtualMachines.find((v) => v.id === id);
|
|
if (!vm) return res.status(404).json({ error: 'VM not found' });
|
|
|
|
if (!Array.isArray(steps) || steps.length === 0) {
|
|
return res.status(400).json({ error: 'Invalid or empty automation steps array' });
|
|
}
|
|
|
|
const logs: string[] = [];
|
|
logs.push(`[Automation Engine] Initialized script execution on VM ${vm.name} (${vm.ipAddress}:${vm.adbPort})`);
|
|
|
|
steps.forEach((step: any, index: number) => {
|
|
const stepNum = index + 1;
|
|
if (typeof step === 'string') {
|
|
if (step.startsWith('START_APP') || step.startsWith('am start')) {
|
|
const pkg = step.split(/\s+/)[1] || 'com.facebook.katana';
|
|
vm.currentAppPackage = pkg;
|
|
logs.push(`Step ${stepNum}: [AM START] Launched target package -> ${pkg}`);
|
|
} else if (step.startsWith('TAP') || step.includes('input tap')) {
|
|
logs.push(`Step ${stepNum}: [INPUT TAP] Simulated touch tap at ${step.replace(/TAP|input tap/g, '').trim()}`);
|
|
} else if (step.startsWith('TEXT') || step.includes('input text')) {
|
|
logs.push(`Step ${stepNum}: [INPUT TEXT] Injected IME text: "${step.replace(/TEXT|input text/g, '').trim()}"`);
|
|
} else if (step.startsWith('INSTALL') || step.includes('adb install')) {
|
|
const pkg = step.split(/\s+/)[1] || 'com.app.newpackage';
|
|
const name = pkg.split('.').pop()?.toUpperCase() || 'NEW APP';
|
|
if (!vm.installedApps.some((a) => a.packageName === pkg)) {
|
|
vm.installedApps.push({
|
|
packageName: pkg,
|
|
appName: name,
|
|
version: '1.0.0',
|
|
iconName: 'Smartphone',
|
|
isSystem: false,
|
|
sizeMb: 32.0,
|
|
});
|
|
}
|
|
logs.push(`Step ${stepNum}: [PACKAGE MANAGER] Stream installed package -> ${pkg}`);
|
|
} else if (step.startsWith('SLEEP')) {
|
|
logs.push(`Step ${stepNum}: [SYSTEM DELAY] Paused thread execution for ${step.split(' ')[1] || '1000'}ms`);
|
|
} else {
|
|
logs.push(`Step ${stepNum}: [ADB COMMAND] Executed: ${step}`);
|
|
}
|
|
} else if (typeof step === 'object' && step.action) {
|
|
if (step.action === 'start_app') {
|
|
vm.currentAppPackage = step.package || 'com.facebook.katana';
|
|
logs.push(`Step ${stepNum}: [AM START] Launched ${vm.currentAppPackage}`);
|
|
} else if (step.action === 'install') {
|
|
const pkg = step.package || 'com.custom.app';
|
|
const name = step.appName || 'Custom App';
|
|
if (!vm.installedApps.some((a) => a.packageName === pkg)) {
|
|
vm.installedApps.push({
|
|
packageName: pkg,
|
|
appName: name,
|
|
version: '1.0.0',
|
|
iconName: 'Smartphone',
|
|
isSystem: false,
|
|
sizeMb: 45.0,
|
|
});
|
|
}
|
|
logs.push(`Step ${stepNum}: [INSTALL] Package ${pkg} (${name}) installed successfully`);
|
|
} else if (step.action === 'tap') {
|
|
logs.push(`Step ${stepNum}: [TAP] Coordinates X:${step.x || 150}, Y:${step.y || 300}`);
|
|
} else if (step.action === 'text') {
|
|
logs.push(`Step ${stepNum}: [TEXT] Typed: "${step.text || 'Hello'}"`);
|
|
} else {
|
|
logs.push(`Step ${stepNum}: [ACTION] Executed ${step.action}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
logs.push(`[Automation Engine] Script finished successfully with 0 errors. All ${steps.length} steps executed.`);
|
|
|
|
res.json({
|
|
status: 'SUCCESS',
|
|
executedStepsCount: steps.length,
|
|
logs,
|
|
currentAppPackage: vm.currentAppPackage,
|
|
installedAppsCount: vm.installedApps.length,
|
|
});
|
|
});
|
|
|
|
// 9. Fastboot Commands
|
|
app.post('/api/vms/:id/fastboot', (req, res) => {
|
|
const { id } = req.params;
|
|
const { command } = req.body;
|
|
|
|
const vm = virtualMachines.find((v) => v.id === id);
|
|
if (!vm) return res.status(404).json({ error: 'VM not found' });
|
|
|
|
let output = 'OKAY [ 0.015s]';
|
|
if (command === 'fastboot oem unlock' || command === 'fastboot flashing unlock') {
|
|
vm.bootloader.isUnlocked = true;
|
|
output = `(...) Unlocking bootloader...\nOKAY [ 0.412s]\nBootloader unlocked successfully.`;
|
|
} else if (command === 'fastboot devices') {
|
|
output = `${vm.id.toUpperCase()}\tfastboot`;
|
|
} else if (command.includes('flash boot')) {
|
|
output = `sending 'boot' (32768 KB)... OKAY [ 0.812s]\nwriting 'boot'... OKAY [ 1.120s]\nFinished. Total time: 1.932s`;
|
|
}
|
|
|
|
res.json({ output, status: 'OKAY' });
|
|
});
|
|
|
|
// 10. Install APK Endpoint
|
|
app.post('/api/vms/:id/install-apk', (req, res) => {
|
|
const { id } = req.params;
|
|
const { appName, packageName, sizeMb } = req.body;
|
|
|
|
const vm = virtualMachines.find((v) => v.id === id);
|
|
if (!vm) return res.status(404).json({ error: 'VM not found' });
|
|
|
|
const newApp = {
|
|
packageName: packageName || `com.example.${appName.toLowerCase().replace(/\s+/g, '')}`,
|
|
appName: appName || 'Custom App',
|
|
version: '1.0.0',
|
|
iconName: 'Smartphone',
|
|
isSystem: false,
|
|
sizeMb: Number(sizeMb) || 18.5,
|
|
};
|
|
|
|
vm.installedApps.push(newApp);
|
|
res.json({ message: `Successfully installed ${newApp.appName} on ${vm.name}`, installedApp: newApp });
|
|
});
|
|
|
|
// 11. Launch App on VM Screen
|
|
app.post('/api/vms/:id/launch-app', (req, res) => {
|
|
const { id } = req.params;
|
|
const { packageName } = req.body;
|
|
|
|
const vm = virtualMachines.find((v) => v.id === id);
|
|
if (!vm) return res.status(404).json({ error: 'VM not found' });
|
|
|
|
vm.currentAppPackage = packageName;
|
|
res.json({ message: `Launched ${packageName} on ${vm.name}`, currentAppPackage: vm.currentAppPackage });
|
|
});
|
|
|
|
// 12. Proxifly Proxy Pool Endpoint
|
|
app.get('/api/proxies/proxifly', (req, res) => {
|
|
const { type, country } = req.query;
|
|
let list = [...PROXIFLY_PROXIES];
|
|
|
|
if (type && typeof type === 'string') {
|
|
list = list.filter((p) => p.type.toUpperCase() === type.toUpperCase());
|
|
}
|
|
if (country && typeof country === 'string') {
|
|
list = list.filter(
|
|
(p) =>
|
|
p.country.toLowerCase().includes(country.toLowerCase()) ||
|
|
p.countryCode.toLowerCase() === country.toLowerCase()
|
|
);
|
|
}
|
|
|
|
res.json({
|
|
source: 'Proxifly Developer Hub (https://proxifly.dev/tools/proxy-list)',
|
|
total: list.length,
|
|
proxies: list,
|
|
});
|
|
});
|
|
|
|
// 13. Test Proxy Endpoint
|
|
app.post('/api/proxies/test', (req, res) => {
|
|
const { host, port, type } = req.body;
|
|
if (!host || !port) {
|
|
return res.status(400).json({ error: 'Host and port required' });
|
|
}
|
|
|
|
const simulatedPing = Math.floor(25 + Math.random() * 60);
|
|
res.json({
|
|
status: 'ONLINE',
|
|
host,
|
|
port: Number(port),
|
|
type: type || 'SOCKS5',
|
|
latencyMs: simulatedPing,
|
|
anonymity: 'Elite',
|
|
ipExit: host,
|
|
message: `Proxy ${host}:${port} (${type || 'SOCKS5'}) connected successfully with ${simulatedPing}ms latency.`,
|
|
});
|
|
});
|
|
|
|
// 14. Assign or Update VM Proxy
|
|
app.post('/api/vms/:id/proxy', (req, res) => {
|
|
const { id } = req.params;
|
|
const { type, host, port, username, password, country, countryCode, anonymity, latencyMs } = req.body;
|
|
|
|
const vm = virtualMachines.find((v) => v.id === id);
|
|
if (!vm) return res.status(404).json({ error: 'VM not found' });
|
|
|
|
if (!host || !port) {
|
|
return res.status(400).json({ error: 'Proxy host and port are required' });
|
|
}
|
|
|
|
vm.proxy = {
|
|
id: `px-${Date.now()}`,
|
|
enabled: true,
|
|
type: type || 'SOCKS5',
|
|
host,
|
|
port: Number(port),
|
|
username,
|
|
password,
|
|
country: country || 'United States',
|
|
countryCode: countryCode || 'US',
|
|
anonymity: anonymity || 'Elite',
|
|
latencyMs: latencyMs || 42,
|
|
externalIp: host,
|
|
lastChecked: new Date().toISOString(),
|
|
};
|
|
|
|
res.json({
|
|
message: `Proxy ${vm.proxy.type}://${vm.proxy.host}:${vm.proxy.port} assigned to ${vm.name}`,
|
|
vm,
|
|
});
|
|
});
|
|
|
|
// 15. Delete / Remove VM Proxy
|
|
app.delete('/api/vms/:id/proxy', (req, res) => {
|
|
const { id } = req.params;
|
|
|
|
const vm = virtualMachines.find((v) => v.id === id);
|
|
if (!vm) return res.status(404).json({ error: 'VM not found' });
|
|
|
|
vm.proxy = undefined;
|
|
res.json({ message: `Proxy removed from ${vm.name}`, vm });
|
|
});
|
|
|
|
// 16. Auto-Assign Unique Proxies to All VMs
|
|
app.post('/api/vms/assign-proxies-auto', (req, res) => {
|
|
if (virtualMachines.length === 0) {
|
|
return res.status(400).json({ error: 'No active VMs found to assign proxies' });
|
|
}
|
|
|
|
const assignments: { vmName: string; proxy: string; country: string }[] = [];
|
|
|
|
virtualMachines.forEach((vm, index) => {
|
|
const proxyData = PROXIFLY_PROXIES[index % PROXIFLY_PROXIES.length];
|
|
vm.proxy = {
|
|
...proxyData,
|
|
enabled: true,
|
|
lastChecked: new Date().toISOString(),
|
|
};
|
|
assignments.push({
|
|
vmName: vm.name,
|
|
proxy: `${vm.proxy.type}://${vm.proxy.host}:${vm.proxy.port}`,
|
|
country: `${vm.proxy.country} (${vm.proxy.countryCode})`,
|
|
});
|
|
});
|
|
|
|
res.json({
|
|
message: `Successfully assigned unique Proxifly proxies to all ${virtualMachines.length} active VMs.`,
|
|
assignments,
|
|
virtualMachines,
|
|
});
|
|
});
|
|
|
|
// ==========================================
|
|
// API KEYS MANAGEMENT
|
|
// ==========================================
|
|
let apiKeys: { id: string; key: string; name: string; createdAt: string; lastUsedAt?: string; requestCount: number; permissions: 'full_control' | 'read_only' }[] = [
|
|
{
|
|
id: 'key-master-01',
|
|
key: 'sk_proxifly_live_mcp_master_key_8842',
|
|
name: 'Claude Desktop & AI Agent Master Key',
|
|
createdAt: new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString(),
|
|
lastUsedAt: new Date().toISOString(),
|
|
requestCount: 142,
|
|
permissions: 'full_control',
|
|
},
|
|
];
|
|
|
|
app.get('/api/keys', (req, res) => {
|
|
res.json({ keys: apiKeys });
|
|
});
|
|
|
|
app.post('/api/keys', (req, res) => {
|
|
const { name, permissions } = req.body;
|
|
const newKey = {
|
|
id: `key-${Date.now()}`,
|
|
key: `sk_proxifly_live_${Math.random().toString(36).substring(2, 12)}_${Math.random().toString(36).substring(2, 10)}`,
|
|
name: name || 'New AI Agent Key',
|
|
createdAt: new Date().toISOString(),
|
|
requestCount: 0,
|
|
permissions: permissions || 'full_control',
|
|
};
|
|
apiKeys.unshift(newKey);
|
|
res.json({ message: 'API key created successfully', apiKey: newKey });
|
|
});
|
|
|
|
app.delete('/api/keys/:id', (req, res) => {
|
|
const { id } = req.params;
|
|
apiKeys = apiKeys.filter((k) => k.id !== id);
|
|
res.json({ message: 'API key revoked' });
|
|
});
|
|
|
|
// ==========================================
|
|
// OPENAPI 3.0 SPECIFICATION
|
|
// ==========================================
|
|
app.get('/api/openapi.json', (req, res) => {
|
|
res.json({
|
|
openapi: '3.0.3',
|
|
info: {
|
|
title: 'Proxifly Android VM Cloud & MCP Control API',
|
|
version: '1.0.0',
|
|
description: 'Programmatic REST API & Model Context Protocol (MCP) Server to provision, manage, automate, and route Android Virtual Machines.',
|
|
},
|
|
servers: [{ url: '/api', description: 'Local Hypervisor Server' }],
|
|
paths: {
|
|
'/vms': {
|
|
get: { summary: 'List all active Android Virtual Machines', responses: { '200': { description: 'Success' } } },
|
|
post: { summary: 'Provision new Android VM instance', responses: { '200': { description: 'Success' } } },
|
|
},
|
|
'/vms/{id}/adb': {
|
|
post: { summary: 'Execute ADB shell command on VM', responses: { '200': { description: 'Success' } } },
|
|
},
|
|
'/vms/{id}/proxy': {
|
|
post: { summary: 'Assign Proxifly residential/datacenter proxy to VM', responses: { '200': { description: 'Success' } } },
|
|
delete: { summary: 'Remove proxy from VM', responses: { '200': { description: 'Success' } } },
|
|
},
|
|
'/mcp': {
|
|
post: { summary: 'MCP JSON-RPC 2.0 Server Protocol Endpoint for AI Agents', responses: { '200': { description: 'Success' } } },
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
// ==========================================
|
|
// MODEL CONTEXT PROTOCOL (MCP) SERVER ENGINE
|
|
// ==========================================
|
|
const MCP_TOOLS_DEFINITIONS = [
|
|
{
|
|
name: 'list_vms',
|
|
description: 'List all active Android Virtual Machines in the hypervisor, including state, IP, ADB ports, hardware profile, and assigned proxy.',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {},
|
|
},
|
|
},
|
|
{
|
|
name: 'create_vm',
|
|
description: 'Provision and launch a new Android Virtual Machine instance.',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string', description: 'Name label for the VM instance' },
|
|
androidVersion: { type: 'string', description: 'Android OS Version (e.g. "14 (UpsideDownCake)", "13 (Tiramisu)", "11 (RedVelvetCake)")' },
|
|
profileId: { type: 'string', description: 'Hardware profile ID ("pixel-8-pro", "galaxy-s24-ultra", "oneplus-12", "aosp-tablet-10")' },
|
|
ramGb: { type: 'number', description: 'RAM size in GB (e.g. 4, 8, 12)' },
|
|
vcpu: { type: 'number', description: 'vCPU cores count (e.g. 2, 4, 8)' },
|
|
},
|
|
required: ['name'],
|
|
},
|
|
},
|
|
{
|
|
name: 'power_vm',
|
|
description: 'Change the power state of an Android VM (start, stop, reboot, recovery).',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
vmId: { type: 'string', description: 'The ID of target Android VM (e.g. "vm-cloud-001")' },
|
|
action: { type: 'string', enum: ['start', 'stop', 'reboot', 'recovery'], description: 'Power action to execute' },
|
|
},
|
|
required: ['vmId', 'action'],
|
|
},
|
|
},
|
|
{
|
|
name: 'execute_adb_command',
|
|
description: 'Execute an arbitrary ADB shell command or input event on a target VM (e.g., "adb shell input tap 300 500", "adb shell input text hello", "adb shell dumpsys battery").',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
vmId: { type: 'string', description: 'Target VM ID' },
|
|
command: { type: 'string', description: 'ADB command to execute (e.g. "input tap 500 800" or "getprop")' },
|
|
},
|
|
required: ['vmId', 'command'],
|
|
},
|
|
},
|
|
{
|
|
name: 'install_apk',
|
|
description: 'Sideload and install an APK application package on a specific Android VM.',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
vmId: { type: 'string', description: 'Target VM ID' },
|
|
appName: { type: 'string', description: 'Human readable app name' },
|
|
packageName: { type: 'string', description: 'Android package name (e.g. "com.facebook.katana")' },
|
|
},
|
|
required: ['vmId', 'packageName'],
|
|
},
|
|
},
|
|
{
|
|
name: 'launch_app',
|
|
description: 'Launch an installed Android application package on target VM.',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
vmId: { type: 'string', description: 'Target VM ID' },
|
|
packageName: { type: 'string', description: 'Package name to open (e.g. "com.whatsapp")' },
|
|
},
|
|
required: ['vmId', 'packageName'],
|
|
},
|
|
},
|
|
{
|
|
name: 'assign_proxy',
|
|
description: 'Assign a residential or datacenter HTTP/SOCKS5 proxy from Proxifly to route a target VM network traffic.',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
vmId: { type: 'string', description: 'Target VM ID' },
|
|
type: { type: 'string', enum: ['HTTP', 'HTTPS', 'SOCKS4', 'SOCKS5'], description: 'Proxy Protocol' },
|
|
host: { type: 'string', description: 'Proxy IP or hostname' },
|
|
port: { type: 'number', description: 'Proxy port number' },
|
|
country: { type: 'string', description: 'Country location' },
|
|
},
|
|
required: ['vmId', 'host', 'port'],
|
|
},
|
|
},
|
|
{
|
|
name: 'run_automation_script',
|
|
description: 'Execute a multi-step macro automation workflow script (tap, swipe, delay, text input) on a VM.',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
vmId: { type: 'string', description: 'Target VM ID' },
|
|
steps: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
type: { type: 'string', enum: ['tap', 'swipe', 'delay', 'typeText', 'launchApp', 'keyEvent'] },
|
|
x: { type: 'number' },
|
|
y: { type: 'number' },
|
|
text: { type: 'string' },
|
|
durationMs: { type: 'number' },
|
|
packageName: { type: 'string' },
|
|
},
|
|
required: ['type'],
|
|
},
|
|
},
|
|
},
|
|
required: ['vmId', 'steps'],
|
|
},
|
|
},
|
|
{
|
|
name: 'get_vm_screenshot',
|
|
description: 'Capture current screenshot state and UI logcat output from target VM.',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
vmId: { type: 'string', description: 'Target VM ID' },
|
|
},
|
|
required: ['vmId'],
|
|
},
|
|
},
|
|
{
|
|
name: 'instagram_orchestration',
|
|
description: 'Perform automated social orchestration actions on Instagram (comment on posts, send direct messages, like content, or verify Google account auth) on target VM.',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
vmId: { type: 'string', description: 'Target Android VM ID' },
|
|
action: { type: 'string', enum: ['comment', 'send_dm', 'like_post', 'verify_google_account'], description: 'Instagram orchestration action' },
|
|
postId: { type: 'string', description: 'Post ID for comments or likes' },
|
|
commentText: { type: 'string', description: 'Comment text to publish' },
|
|
targetUsername: { type: 'string', description: 'Target user handle for direct messages' },
|
|
messageText: { type: 'string', description: 'DM text content' },
|
|
googleEmail: { type: 'string', description: 'Google account email for 2FA verification' },
|
|
},
|
|
required: ['vmId', 'action'],
|
|
},
|
|
},
|
|
];
|
|
|
|
// Helper to process MCP Tool Calls
|
|
async function handleMcpToolCall(toolName: string, args: any) {
|
|
switch (toolName) {
|
|
case 'list_vms':
|
|
return {
|
|
total: virtualMachines.length,
|
|
vms: virtualMachines.map((v) => ({
|
|
id: v.id,
|
|
name: v.name,
|
|
status: v.status,
|
|
androidVersion: v.androidVersion,
|
|
ipAddress: v.ipAddress,
|
|
adbPort: v.adbPort,
|
|
currentAppPackage: v.currentAppPackage,
|
|
proxy: v.proxy ? `${v.proxy.type}://${v.proxy.host}:${v.proxy.port}` : 'None (Direct)',
|
|
batteryLevel: v.batteryLevel,
|
|
})),
|
|
};
|
|
|
|
case 'create_vm': {
|
|
const { name, androidVersion, profileId, ramGb, vcpu } = args;
|
|
const profile = (HardwareProfiles as any)[profileId] || HardwareProfiles.PIXEL_8_PRO;
|
|
const newVm: AndroidVM = {
|
|
id: `vm-cloud-${Math.floor(100 + Math.random() * 900)}`,
|
|
name: name || 'New AI Managed VM',
|
|
androidVersion: (androidVersion as AndroidVersion) || '14 (UpsideDownCake)',
|
|
arch: 'x86_64',
|
|
profile,
|
|
ramGb: Number(ramGb) || 8,
|
|
vcpu: Number(vcpu) || 4,
|
|
storageGb: 64,
|
|
bootloader: {
|
|
isUnlocked: true,
|
|
rootMethod: 'magisk',
|
|
customRecovery: true,
|
|
selinuxMode: 'permissive',
|
|
kernelCmdline: 'console=ttyMSM0,115200 androidboot.hardware=qcom androidboot.selinux=permissive',
|
|
gpuAcceleration: 'swiftshader',
|
|
},
|
|
status: 'running',
|
|
ipAddress: `192.168.122.${Math.floor(10 + Math.random() * 200)}`,
|
|
adbPort: 5555,
|
|
vncPort: 5901,
|
|
batteryLevel: 100,
|
|
isCharging: true,
|
|
orientation: 'portrait',
|
|
location: { latitude: 37.7749, longitude: -122.4194, addressName: 'San Francisco, CA' },
|
|
metrics: { cpuUsagePct: 12, ramUsageMb: 1840, totalRamMb: 8192, fps: 60, networkRxKbps: 45, networkTxKbps: 18, uptimeSeconds: 10 },
|
|
installedApps: [...DEFAULT_ANDROID_APPS],
|
|
currentAppPackage: 'com.android.launcher3',
|
|
createdAt: new Date().toISOString(),
|
|
snapshotCount: 0,
|
|
};
|
|
virtualMachines.push(newVm);
|
|
syncActiveVmCount();
|
|
return { message: `VM ${newVm.name} created successfully`, vm: newVm };
|
|
}
|
|
|
|
case 'power_vm': {
|
|
const { vmId, action } = args;
|
|
const vm = virtualMachines.find((v) => v.id === vmId);
|
|
if (!vm) throw new Error(`VM ${vmId} not found`);
|
|
|
|
if (action === 'stop') vm.status = 'stopped';
|
|
else if (action === 'start') vm.status = 'running';
|
|
else if (action === 'reboot') vm.status = 'booting';
|
|
else if (action === 'recovery') vm.status = 'recovery';
|
|
|
|
return { message: `VM ${vm.name} power action executed: ${action}`, status: vm.status };
|
|
}
|
|
|
|
case 'execute_adb_command': {
|
|
const { vmId, command } = args;
|
|
const vm = virtualMachines.find((v) => v.id === vmId);
|
|
if (!vm) throw new Error(`VM ${vmId} not found`);
|
|
|
|
const cmd = (command || '').trim();
|
|
let output = `Executed ADB command: ${cmd}\n`;
|
|
if (cmd.includes('tap')) {
|
|
output += `[Input Event] Injected touch tap event on display layer.`;
|
|
} else if (cmd.includes('text')) {
|
|
output += `[Input Event] Injected keyboard text buffer into active focus field.`;
|
|
} else {
|
|
output += `Exit Code: 0 (OKAY)`;
|
|
}
|
|
return { vmId, command: cmd, output, exitCode: 0 };
|
|
}
|
|
|
|
case 'install_apk': {
|
|
const { vmId, appName, packageName } = args;
|
|
const vm = virtualMachines.find((v) => v.id === vmId);
|
|
if (!vm) throw new Error(`VM ${vmId} not found`);
|
|
|
|
const cleanPkg = packageName || 'com.example.app';
|
|
const cleanName = appName || cleanPkg.split('.').pop() || 'App';
|
|
|
|
if (!vm.installedApps.find((a) => a.packageName === cleanPkg)) {
|
|
vm.installedApps.push({
|
|
packageName: cleanPkg,
|
|
appName: cleanName,
|
|
version: '1.0.0',
|
|
iconName: 'Smartphone',
|
|
isSystem: false,
|
|
sizeMb: 42,
|
|
});
|
|
}
|
|
vm.currentAppPackage = cleanPkg;
|
|
return { message: `Successfully installed ${cleanName} (${cleanPkg}) on ${vm.name}`, installedAppsCount: vm.installedApps.length };
|
|
}
|
|
|
|
case 'launch_app': {
|
|
const { vmId, packageName } = args;
|
|
const vm = virtualMachines.find((v) => v.id === vmId);
|
|
if (!vm) throw new Error(`VM ${vmId} not found`);
|
|
|
|
vm.currentAppPackage = packageName;
|
|
return { message: `Launched ${packageName} on ${vm.name}`, currentAppPackage: vm.currentAppPackage };
|
|
}
|
|
|
|
case 'assign_proxy': {
|
|
const { vmId, type, host, port, country } = args;
|
|
const vm = virtualMachines.find((v) => v.id === vmId);
|
|
if (!vm) throw new Error(`VM ${vmId} not found`);
|
|
|
|
vm.proxy = {
|
|
id: `px-mcp-${Date.now()}`,
|
|
enabled: true,
|
|
type: (type as ProxyType) || 'SOCKS5',
|
|
host,
|
|
port: Number(port),
|
|
country: country || 'United States',
|
|
countryCode: 'US',
|
|
anonymity: 'Elite',
|
|
latencyMs: 38,
|
|
externalIp: host,
|
|
lastChecked: new Date().toISOString(),
|
|
};
|
|
return { message: `Assigned proxy ${vm.proxy.type}://${vm.proxy.host}:${vm.proxy.port} to ${vm.name}`, vmProxy: vm.proxy };
|
|
}
|
|
|
|
case 'run_automation_script': {
|
|
const { vmId, steps } = args;
|
|
const vm = virtualMachines.find((v) => v.id === vmId);
|
|
if (!vm) throw new Error(`VM ${vmId} not found`);
|
|
|
|
const logs: string[] = [];
|
|
for (let i = 0; i < (steps || []).length; i++) {
|
|
const step = steps[i];
|
|
if (step.type === 'tap') logs.push(`Step ${i + 1}: Tap at (${step.x}, ${step.y})`);
|
|
else if (step.type === 'typeText') logs.push(`Step ${i + 1}: Typed text "${step.text}"`);
|
|
else if (step.type === 'launchApp') {
|
|
vm.currentAppPackage = step.packageName || vm.currentAppPackage;
|
|
logs.push(`Step ${i + 1}: Launched ${step.packageName}`);
|
|
} else logs.push(`Step ${i + 1}: Executed ${step.type}`);
|
|
}
|
|
|
|
return { message: `Executed automation sequence (${(steps || []).length} steps) on ${vm.name}`, logs };
|
|
}
|
|
|
|
case 'get_vm_screenshot': {
|
|
const { vmId } = args;
|
|
const vm = virtualMachines.find((v) => v.id === vmId);
|
|
if (!vm) throw new Error(`VM ${vmId} not found`);
|
|
|
|
return {
|
|
vmId: vm.id,
|
|
vmName: vm.name,
|
|
currentAppPackage: vm.currentAppPackage,
|
|
status: vm.status,
|
|
timestamp: new Date().toISOString(),
|
|
screenshotCaptured: true,
|
|
resolution: vm.profile.screenResolution,
|
|
};
|
|
}
|
|
|
|
case 'instagram_orchestration': {
|
|
const { vmId, action, postId, commentText, targetUsername, messageText, googleEmail } = args;
|
|
const vm = virtualMachines.find((v) => v.id === vmId);
|
|
if (!vm) throw new Error(`VM ${vmId} not found`);
|
|
|
|
if (action === 'comment') {
|
|
return {
|
|
status: 'success',
|
|
vmId: vm.id,
|
|
action: 'comment',
|
|
postId: postId || 'ig-1',
|
|
commentText,
|
|
publishedAt: new Date().toISOString(),
|
|
proxyRoute: vm.proxy ? `${vm.proxy.type}://${vm.proxy.host}` : 'Direct',
|
|
message: `Successfully posted comment on Instagram post ${postId || 'ig-1'}: "${commentText}"`,
|
|
};
|
|
} else if (action === 'send_dm') {
|
|
return {
|
|
status: 'success',
|
|
vmId: vm.id,
|
|
action: 'send_dm',
|
|
targetUsername: targetUsername || '@android_dev_official',
|
|
messageText,
|
|
deliveredAt: new Date().toISOString(),
|
|
message: `Direct message sent to ${targetUsername || '@android_dev_official'}: "${messageText}"`,
|
|
};
|
|
} else if (action === 'like_post') {
|
|
return {
|
|
status: 'success',
|
|
vmId: vm.id,
|
|
action: 'like_post',
|
|
postId: postId || 'ig-1',
|
|
message: `Liked Instagram post ${postId || 'ig-1'}`,
|
|
};
|
|
} else if (action === 'verify_google_account') {
|
|
return {
|
|
status: 'success',
|
|
vmId: vm.id,
|
|
action: 'verify_google_account',
|
|
googleEmail: googleEmail || 'alex.dev.cloud@gmail.com',
|
|
verificationStatus: 'verified_2fa_code_passed',
|
|
codeReceived: '482910',
|
|
message: `Google Account ${googleEmail || 'alex.dev.cloud@gmail.com'} successfully authenticated with 2FA email verification code.`,
|
|
};
|
|
}
|
|
return { status: 'executed', action };
|
|
}
|
|
|
|
default:
|
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
}
|
|
}
|
|
|
|
// MCP JSON-RPC 2.0 Handler
|
|
app.post('/api/mcp', async (req, res) => {
|
|
const { jsonrpc, id, method, params } = req.body || {};
|
|
|
|
try {
|
|
if (method === 'initialize') {
|
|
return res.json({
|
|
jsonrpc: '2.0',
|
|
id: id || 1,
|
|
result: {
|
|
protocolVersion: '2024-11-05',
|
|
capabilities: {
|
|
tools: {
|
|
listChanged: true,
|
|
},
|
|
},
|
|
serverInfo: {
|
|
name: 'proxifly-android-mcp-server',
|
|
version: '1.0.0',
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
if (method === 'tools/list') {
|
|
return res.json({
|
|
jsonrpc: '2.0',
|
|
id: id || 1,
|
|
result: {
|
|
tools: MCP_TOOLS_DEFINITIONS,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (method === 'tools/call') {
|
|
const { name, arguments: toolArgs } = params || {};
|
|
const resultData = await handleMcpToolCall(name, toolArgs || {});
|
|
|
|
return res.json({
|
|
jsonrpc: '2.0',
|
|
id: id || 1,
|
|
result: {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: JSON.stringify(resultData, null, 2),
|
|
},
|
|
],
|
|
},
|
|
});
|
|
}
|
|
|
|
return res.status(400).json({
|
|
jsonrpc: '2.0',
|
|
id: id || 1,
|
|
error: {
|
|
code: -32601,
|
|
message: `Method not found: ${method}`,
|
|
},
|
|
});
|
|
} catch (err: any) {
|
|
return res.json({
|
|
jsonrpc: '2.0',
|
|
id: id || 1,
|
|
error: {
|
|
code: -32603,
|
|
message: err?.message || 'Internal MCP tool execution error',
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
// SSE Stream for MCP Client Agents
|
|
app.get('/api/mcp/sse', (req, res) => {
|
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
res.setHeader('Connection', 'keep-alive');
|
|
|
|
res.write(`event: endpoint\ndata: /api/mcp\n\n`);
|
|
|
|
const keepAliveInterval = setInterval(() => {
|
|
res.write(`: keepalive\n\n`);
|
|
}, 15000);
|
|
|
|
req.on('close', () => {
|
|
clearInterval(keepAliveInterval);
|
|
});
|
|
});
|
|
|
|
// Start Express Server with Vite Middleware
|
|
|
|
async function startServer() {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
const vite = await createViteServer({
|
|
server: { middlewareMode: true },
|
|
appType: 'spa',
|
|
});
|
|
app.use(vite.middlewares);
|
|
} else {
|
|
app.use(express.static(path.resolve(__dirname, 'dist')));
|
|
app.get('*', (req, res) => {
|
|
res.sendFile(path.resolve(__dirname, 'dist', 'index.html'));
|
|
});
|
|
}
|
|
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
console.log(`Android Cloud Hypervisor Server running on http://0.0.0.0:${PORT}`);
|
|
});
|
|
}
|
|
|
|
startServer();
|