Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Application } from 'express';
import { CliArg, ISessionCapability, MockConfig, RecordConfig, RequestInfo, ReplayConfig, SniffConfig } from './types';
import { DefaultPluginArgs, IPluginArgs } from './interfaces';
import _ from 'lodash';
import { configureWifiProxy, isRealDevice, getGlobalProxyValue } from './utils/adb';
import { configureWifiProxy, isRealDevice, getCurrentWifiProxyConfig } from './utils/adb';
import { cleanUpProxyServer, sanitizeMockConfig, setupProxyServer } from './utils/proxy';
import proxyCache from './proxy-cache';
import logger from './logger';
Expand Down Expand Up @@ -103,8 +103,8 @@ export class AppiumInterceptorPlugin extends BasePlugin {
return response;
}
const realDevice = await isRealDevice(adb, deviceUDID);
const currentGlobalProxy = await getGlobalProxyValue(adb, deviceUDID)
const proxy = await setupProxyServer(sessionId, deviceUDID, realDevice, certDirectory, currentGlobalProxy);
const currentWifiProxyConfig = await getCurrentWifiProxyConfig(adb, deviceUDID)
const proxy = await setupProxyServer(sessionId, deviceUDID, realDevice, certDirectory, currentWifiProxyConfig);
await configureWifiProxy(adb, deviceUDID, realDevice, proxy.options);
proxyCache.add(sessionId, proxy);
}
Expand Down
4 changes: 2 additions & 2 deletions src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface ProxyOptions {
certificatePath: string;
port: number;
ip: string;
previousGlobalProxy?: ProxyOptions;
previousConfig?: ProxyOptions;
}

export class Proxy {
Expand Down Expand Up @@ -83,7 +83,7 @@ export class Proxy {
}

public get previousGlobalProxy(): ProxyOptions | undefined {
return this.options.previousGlobalProxy ?? undefined
return this.options.previousConfig ?? undefined
}

public async start(): Promise<boolean> {
Expand Down
98 changes: 77 additions & 21 deletions src/utils/adb.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ADB from 'appium-adb';
import { Proxy, ProxyOptions } from '../proxy';
import logger from '../logger';

export type ADBInstance = ADB;
export type UDID = string;
Expand All @@ -25,17 +26,42 @@ export async function isRealDevice(adb: ADBInstance, udid: UDID): Promise<boolea
return property !== 'emulator';
}

/**
* Configures the global HTTP proxy settings for Wi-Fi traffic on the target Android device via ADB.
* If a valid proxy configuration is provided:
* 1. It sets up an 'adb reverse' tunnel for real devices to ensure the device can reach the host-side proxy.
* 2. It sets the 'http_proxy' global setting to 'IP:PORT'.
* If the configuration is invalid or missing, it sets the 'http_proxy' to ':0' (which disables the proxy).
*
* @param adb - The ADB instance established by Appium.
* @param udid - The Unique Device Identifier (UDID) of the Android device or emulator.
* @param isRealDevice - Boolean indicating if the target is a physical device (requires adb reverse).
* @param proxyConfig - Optional configuration object containing the IP and port for the proxy.
* @returns A Promise resolving to the output of the final ADB shell command.
* @throws {Error} Throws an error if any ADB command execution fails.
*/
export async function configureWifiProxy(
adb: ADBInstance,
udid: UDID,
realDevice: boolean,
proxy?: ProxyOptions
isRealDevice: boolean,
proxyConfig?: ProxyOptions
): Promise<string> {
logger.info(`configureWifiProxy(udid=${udid}, isRealDevice=${isRealDevice}, proxyConfig=${JSON.stringify(proxyConfig)})`)
try {
const host = proxy ? `${proxy.ip}:${proxy.port}` : ':0';
const isConfigValid = proxyConfig
&& proxyConfig.ip
&& proxyConfig.ip.trim().length > 0
&& !isNaN(proxyConfig.port)
&& proxyConfig.port > 0;

if (realDevice && proxy) {
await adbExecWithDevice(adb, udid, ['reverse', `tcp:${proxy.port}`, `tcp:${proxy.port}`]);
if (!isConfigValid) {
logger.warn(`Invalid proxy config: ${JSON.stringify(proxyConfig)}. Proxy will be disabled for udid ${udid}.`);
}

const host = isConfigValid ? `${proxyConfig.ip}:${proxyConfig.port}` : ':0';

if (isRealDevice && isConfigValid) {
await adbExecWithDevice(adb, udid, ['reverse', `tcp:${proxyConfig.port}`, `tcp:${proxyConfig.port}`]);
}

return await adbExecWithDevice(adb, udid, [
Expand All @@ -51,36 +77,66 @@ export async function configureWifiProxy(
}
}

export async function getGlobalProxyValue(
/**
* Retrieves the current global HTTP proxy settings from the target Android device via ADB.
* The function checks the 'http_proxy' setting in the 'global' namespace of the Android system settings.
*
* @param adb - The ADB instance established by Appium.
* @param udid - The Unique Device Identifier (UDID) of the Android device or emulator.
* @returns A Promise resolving to an object containing the IP and port of the proxy
* ({ ip: string, port: number }), or undefined if no proxy is configured,
* or if the configuration is invalid (e.g., malformed port).
* @throws {Error} Throws an error if the ADB command execution fails.
*/
export async function getCurrentWifiProxyConfig(
adb: ADBInstance,
udid: UDID
): Promise<ProxyOptions> {
): Promise<ProxyOptions | undefined> {
logger.info(`getCurrentWifiProxyConfig(udid=${udid})`);
try {
const proxy = await adbExecWithDevice(adb, udid, [
// Execute ADB command to get the current global HTTP proxy setting
const proxySettingsCommandResult = await adbExecWithDevice(adb, udid, [
'shell',
'settings',
'get',
'global',
'http_proxy'
])
'http_proxy',
]);

if(proxy == ":0" || proxy == "null") {
return {
port: 0
} as ProxyOptions
// ADB returns ":0" or "null" when the proxy is disabled.
if (!proxySettingsCommandResult || proxySettingsCommandResult === ':0' || proxySettingsCommandResult === 'null') {
logger.info(`No active proxy for udid ${udid}.`);
return undefined;
}

const [ip, portStr] = proxy.split(":");
// Ensure the format is IP:PORT (must contain at least one ':').
if (!proxySettingsCommandResult.includes(':')) {
logger.warn(`Invalid proxy settings format detected for udid ${udid}: '${proxySettingsCommandResult}'.`);
return undefined;
}

// Split the string into IP and port
const [ip, portStr] = proxySettingsCommandResult.split(':', 2);
const port = Number(portStr);

return {
ip: ip,
port: port
} as ProxyOptions
// Validate IP and port values.
// IP should not be empty after trimming, and port must be a valid number greater than 0.
if (!ip.trim() || isNaN(port) || port <= 0) {
logger.warn(`Invalid proxy settings detected for udid ${udid}: (ip=${ip}, port=${port})`);
return undefined;
}

const proxyOptions: ProxyOptions = {
ip: ip.trim(),
port: port,
} as ProxyOptions;

logger.info(`Found active proxy for udid ${udid}: ${JSON.stringify(proxyOptions)}`);
return proxyOptions;

} catch (error: any) {
throw new Error(`Error get global proxy value ${udid}: ${error.message}`);
}
throw new Error(`Error getting wifi proxy settings for ${udid}: ${error.message}`);
}
}


Expand Down
4 changes: 2 additions & 2 deletions src/utils/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ export async function setupProxyServer(
deviceUDID: string,
isRealDevice: boolean,
certDirectory: string,
currentGlobalProxy?: ProxyOptions
currentWifiProxyConfig?: ProxyOptions
) {
const certificatePath = prepareCertificate(sessionId, certDirectory);
const port = await getPort();
const _ip = isRealDevice ? 'localhost' : ip.address('public', 'ipv4');
const proxy = new Proxy({ deviceUDID, sessionId, certificatePath, port, ip: _ip, previousGlobalProxy: currentGlobalProxy });
const proxy = new Proxy({ deviceUDID, sessionId, certificatePath, port, ip: _ip, previousConfig: currentWifiProxyConfig });
await proxy.start();
if (!proxy.isStarted()) {
throw new Error('Unable to start the proxy server');
Expand Down