From e9a6374385f0c5f13e1a955d9ebfbdec3c2a5441 Mon Sep 17 00:00:00 2001
From: P-Courteille
Date: Tue, 14 Oct 2025 12:14:12 +0200
Subject: [PATCH 1/3] customCertificate
---
package.json | 6 +++++-
src/config.ts | 5 -----
src/interfaces.ts | 9 +++++++++
src/plugin.ts | 7 ++++++-
src/scripts/test-connection.ts | 9 ++++++---
src/utils/proxy.ts | 15 +++++++++------
6 files changed, 35 insertions(+), 16 deletions(-)
delete mode 100644 src/config.ts
create mode 100644 src/interfaces.ts
diff --git a/package.json b/package.json
index b221196..7cac9c8 100644
--- a/package.json
+++ b/package.json
@@ -75,7 +75,11 @@
"$schema": "http://json-schema.org/draft-07/schema",
"additionalProperties": false,
"description": "Appium configuration schema for the interceptor plugin.",
- "properties": {},
+ "properties": {
+ "certdirectory": {
+ "type": "string"
+ }
+ },
"title": "Appium interceptor plugin",
"type": "object"
},
diff --git a/src/config.ts b/src/config.ts
deleted file mode 100644
index 8a2f633..0000000
--- a/src/config.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import path from 'path';
-
-export default {
- certDirectory: path.join(__dirname, '..', 'certificate'),
-};
diff --git a/src/interfaces.ts b/src/interfaces.ts
new file mode 100644
index 0000000..6176ff3
--- /dev/null
+++ b/src/interfaces.ts
@@ -0,0 +1,9 @@
+import path from 'path';
+
+export interface IPluginArgs {
+ certdirectory: string;
+}
+
+export const DefaultPluginArgs: IPluginArgs = {
+ certdirectory: path.join(__dirname, '..', 'certificate'),
+};
\ No newline at end of file
diff --git a/src/plugin.ts b/src/plugin.ts
index 9e3415d..2c3b46a 100644
--- a/src/plugin.ts
+++ b/src/plugin.ts
@@ -2,6 +2,7 @@ import { BasePlugin } from 'appium/plugin';
import http from 'http';
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 } from './utils/adb';
import { cleanUpProxyServer, sanitizeMockConfig, setupProxyServer } from './utils/proxy';
@@ -10,6 +11,7 @@ import logger from './logger';
import log from './logger';
export class AppiumInterceptorPlugin extends BasePlugin {
+ private pluginArgs: IPluginArgs = Object.assign({}, DefaultPluginArgs);
static executeMethodMap = {
'interceptor: addMock': {
command: 'addMock',
@@ -68,7 +70,9 @@ export class AppiumInterceptorPlugin extends BasePlugin {
};
constructor(name: string, cliArgs: CliArg) {
+ log.debug(`📱 Plugin Args: ${JSON.stringify(cliArgs)}`);
super(name, cliArgs);
+ this.pluginArgs = Object.assign({}, DefaultPluginArgs, cliArgs as unknown as IPluginArgs);
}
static async updateServer(expressApp: Application, httpServer: http.Server, cliArgs: CliArg) {}
@@ -89,6 +93,7 @@ export class AppiumInterceptorPlugin extends BasePlugin {
const mergedCaps = { ...caps.alwaysMatch, ..._.get(caps, 'firstMatch[0]', {}) };
const interceptFlag = mergedCaps['appium:intercept'];
const { deviceUDID, platformName } = response.value[1];
+ const certDirectory = this.pluginArgs.certdirectory;
const sessionId = response.value[0];
const adb = driver.sessions[sessionId]?.adb;
@@ -98,7 +103,7 @@ export class AppiumInterceptorPlugin extends BasePlugin {
return response;
}
const realDevice = await isRealDevice(adb, deviceUDID);
- const proxy = await setupProxyServer(sessionId, deviceUDID, realDevice);
+ const proxy = await setupProxyServer(sessionId, deviceUDID, realDevice, certDirectory);
await configureWifiProxy(adb, deviceUDID, realDevice, proxy);
proxyCache.add(sessionId, proxy);
}
diff --git a/src/scripts/test-connection.ts b/src/scripts/test-connection.ts
index 8a93c88..4574613 100644
--- a/src/scripts/test-connection.ts
+++ b/src/scripts/test-connection.ts
@@ -7,13 +7,16 @@ import { ADBInstance, UDID, configureWifiProxy, isRealDevice, openUrl } from '..
import { v4 as uuid } from 'uuid';
import { setupProxyServer } from '../utils/proxy';
import { Proxy } from '../proxy';
+import path from 'path';
type VerifyOptions = {
udid: string;
+ certdirectory: string;
};
const defaultOptions: VerifyOptions = {
udid: '',
+ certdirectory: path.join(__dirname, '..', 'certificate'),
};
const MOCK_BACKEND_HTML = `Appium Mock
@@ -76,9 +79,9 @@ async function addMock(proxy: Proxy) {
});
}
-async function verifyDeviceConnection(adb: ADBInstance, udid: UDID) {
+async function verifyDeviceConnection(adb: ADBInstance, udid: UDID, certDirectory: string) {
const realDevice = await isRealDevice(adb, udid);
- const proxy = await setupProxyServer(uuid(), udid, realDevice);
+ const proxy = await setupProxyServer(uuid(), udid, realDevice, certDirectory);
addMock(proxy);
await configureWifiProxy(adb, udid, realDevice, proxy);
await openUrl(adb, udid, MOCK_BACKEND_URL);
@@ -102,7 +105,7 @@ async function main() {
const options = getOptions();
const udid = await pickDeviceToTest(adb, options);
await registerExitHook(adb, udid);
- await verifyDeviceConnection(adb, udid);
+ await verifyDeviceConnection(adb, udid, options.certdirectory);
}
main().catch(console.log);
diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts
index d9cf5c3..fdae61e 100644
--- a/src/utils/proxy.ts
+++ b/src/utils/proxy.ts
@@ -13,7 +13,6 @@ import { Proxy } from '../proxy';
import ip from 'ip';
import os from 'os';
import path from 'path';
-import config from '../config';
import fs from 'fs-extra';
import { minimatch } from 'minimatch';
import http from 'http';
@@ -112,9 +111,10 @@ export function modifyResponseBody(ctx: IContext, mockConfig: MockConfig) {
export async function setupProxyServer(
sessionId: string,
deviceUDID: string,
- isRealDevice: boolean
+ isRealDevice: boolean,
+ certDirectory: string,
) {
- const certificatePath = prepareCertificate(sessionId);
+ 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 });
@@ -131,9 +131,12 @@ export async function cleanUpProxyServer(proxy: Proxy) {
fs.rmdirSync(proxy.certificatePath, { recursive: true, force: true });
}
-function prepareCertificate(sessionId: string) {
+function prepareCertificate(sessionId: string, certDirectory: string) {
const sessionCertDirectory = path.join(os.tmpdir(), sessionId);
- fs.copySync(config.certDirectory, sessionCertDirectory);
+ if(fs.existsSync(certDirectory)){
+ log.errorWithException(`Error certDirectory doesn't exist (${certDirectory})`);
+ }
+ fs.copySync(certDirectory, sessionCertDirectory);
return sessionCertDirectory;
}
@@ -203,7 +206,7 @@ export function doesUrlMatch(pattern: UrlPattern, url: string) {
? jsonOrStringUrl.test(url)
: minimatch(url, jsonOrStringUrl);
} catch (err) {
- log.error(`Error validaing url ${pattern} against url ${url}`);
+ log.error(`Error validating url ${pattern} against url ${url}`);
return false;
}
}
From 1b291beadc9aab89d527e783e9bac598eed2ce3d Mon Sep 17 00:00:00 2001
From: P-Courteille
Date: Tue, 14 Oct 2025 12:43:54 +0200
Subject: [PATCH 2/3] Readme update
---
README.md | 8 ++++++++
docs/commands.md | 2 +-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 9e549f7..accfb80 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,14 @@ The plugin will not be active unless turned on when invoking the Appium server:
`appium server -ka 800 --use-plugins=appium-interceptor -pa /wd/hub`
+## Custom certificate
+
+If you need to use a custom certificate, it can be done by passing `certdirectory` as an argument of the plugin:
+
+`appium server -ka 800 --use-plugins=appium-interceptor --plugin-appium-interceptor-certdirectory="" -pa /wd/hub`
+
+Please keep the same directory structure as the existing certificate folder.
+
## what does this plugin do?
For every appium session, interceptor plugin will start a proxy server and updates the device proxy settings to pass all network traffic to proxy server. Mocking is disabled by default and can be enabled from the test by passing `appium:intercept : true` in the desired capability while creating a new appium session.
diff --git a/docs/commands.md b/docs/commands.md
index cca7ab4..54db1d3 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -33,7 +33,7 @@ Mock configuration is a json object that defines the specification for filtering
| statusCode | number | no | Updates the response status code with the given value | To simulate any unexpected error you can send some of the below statusCode
1. `500` - Internal server error
2. `400` - Bad request
3. `401` - Unauthorized |
| responseHeaders | object | no | Map of key value pairs to be added or removed from the response header | Same syntax as `headers` key. But this will update the response header |
| responseBody | object | no | This will replace the original response data returned by the api server and updates it with new data | Passing the config as `{"url" : "/api/login/g" , "responseBody": "{\"error\": \"User account locked\"}", `statusCode`: 400 }` will simulate a error scenario when logged in with any user credentilas |
-| updateResponseBody | string | no | This is similar to responseBody but instead of fully mocking the server response, you can replace any value in the response using Regular expression or jsonpath | Consider you application returns user data as `{\"username\": \"someusername\", \"email\": \"someemail@email.com\", \"isAdmin\" : \"false\"}` as a response for get user api request and you want to update the values for email and IsAdmin fiels, then you can pass
`{"updateRequestBody": [{ "jsonPath": "$.email", "newemail@email.com" }, { "jsonPath": "$.isAdmin", "true" }]}` and it will update the email and isAdmin field before sending the response back to the application|
+| updateResponseBody | string | no | This is similar to responseBody but instead of fully mocking the server response, you can replace any value in the response using Regular expression or jsonpath | Consider you application returns user data as `{\"username\": \"someusername\", \"email\": \"someemail@email.com\", \"isAdmin\" : \"false\"}` as a response for get user api request and you want to update the values for email and IsAdmin fiels, then you can pass
`{"updateRequestBody": [{ "jsonPath": "$.email", value: "newemail@email.com" }, { "jsonPath": "$.isAdmin", value: "true" }]}` and it will update the email and isAdmin field before sending the response back to the application|
## Commands:
From ba102c455cf89d65ee9e5cb61dc93fadb9320354 Mon Sep 17 00:00:00 2001
From: P-Courteille
Date: Tue, 14 Oct 2025 18:27:22 +0200
Subject: [PATCH 3/3] hotfix
---
package-lock.json | 4 ++--
src/utils/proxy.ts | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 308cba9..1d036bb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "appium-interceptor",
- "version": "1.0.2",
+ "version": "1.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "appium-interceptor",
- "version": "1.0.2",
+ "version": "1.0.3",
"license": "ISC",
"dependencies": {
"@appium/support": "^7.0.0",
diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts
index fdae61e..0e9e18f 100644
--- a/src/utils/proxy.ts
+++ b/src/utils/proxy.ts
@@ -133,8 +133,8 @@ export async function cleanUpProxyServer(proxy: Proxy) {
function prepareCertificate(sessionId: string, certDirectory: string) {
const sessionCertDirectory = path.join(os.tmpdir(), sessionId);
- if(fs.existsSync(certDirectory)){
- log.errorWithException(`Error certDirectory doesn't exist (${certDirectory})`);
+ if(!fs.existsSync(certDirectory)){
+ throw new Error(`Error certDirectory doesn't exist (${certDirectory})`);
}
fs.copySync(certDirectory, sessionCertDirectory);
return sessionCertDirectory;