Proxy Chaining (Pre-Proxy)
Relevant source files
The following files were used as context for generating this wiki page:The Proxy Chaining feature, also referred to as "Pre-Proxy," allows users to route a browser profile's network traffic through an additional, intermediate proxy server before it reaches the main proxy server assigned to that profile. This creates a [Local] -> [Pre-Proxy] -> [Target Node] -> [Web] architecture. The primary purpose of this feature is to enhance privacy and security by hiding the user's real IP address from the final proxy provider.
This system is built upon the powerful routing capabilities of the integrated Xray-core. It supports multiple operational modes, manual and subscription-based proxy management, and both global and per-profile configuration overrides.
Sources: README.md:31, README_zh.md:33, main.js:300-322
Architecture
The proxy chaining functionality is achieved by dynamically generating a specific Xray-core configuration file each time a browser profile is launched. The core component responsible for this is the generateXrayConfig function. When a pre-proxy is active, this function creates an Xray configuration with at least two outbound connections: one for the pre-proxy (proxy_pre) and one for the main profile proxy (proxy_main).
The main proxy's outbound configuration is then modified to use the pre-proxy as its next hop by setting its proxySettings to point to the pre-proxy's tag. This effectively creates the chain. The browser instance is then launched with arguments to use the local SOCKS5 server created by this Xray instance.
Sources: utils.js:283-305, main.js:401-402
The following diagram illustrates the data flow when proxy chaining is enabled.
Sources: main.js:425-428, utils.js:283-305
Configuration and Management
The Pre-Proxy system has a flexible configuration model, allowing for global settings that can be overridden on a per-profile basis. All settings are managed through the UI and stored in settings.json in the user data directory.
Sources: renderer.js:311, main.js:300-302
Global Settings
Global pre-proxy settings are managed via the "Manage Chain" modal. These settings apply to all profiles unless a specific profile has an override.
| Setting | UI Element | Description | Source File |
|---|---|---|---|
enablePreProxy | "Enable Chain Proxy" checkbox | A boolean flag that globally enables or disables the pre-proxy feature. | renderer.js:312, index.html:1003 |
mode | "Mode" dropdown | Defines the proxy selection strategy. Can be single, balance, or failover. | renderer.js:1033, index.html:1083 |
preProxies | Proxy Node List | An array of proxy node objects added manually or via subscriptions. | renderer.js:312 |
subscriptions | Subscription List | An array of subscription objects used to fetch proxy nodes. | renderer.js:312 |
notify | "Notify" checkbox | If checked, shows a system notification when a proxy is auto-switched in balance or failover mode. | renderer.js:1034, index.html:1085 |
Sources: renderer.js:311-313, 1033-1034, main.js:300-302
Per-Profile Overrides
Each browser profile can override the global enablePreProxy setting. This provides granular control over which profiles use the proxy chain.
| Override Value | UI Text | Behavior |
|---|---|---|
default | Pre: Global / 跟随全局 | The profile follows the global enablePreProxy setting. |
on | Pre: ON / 强制开启 | Forces the use of the pre-proxy for this profile, regardless of the global setting. |
off | Pre: OFF / 强制关闭 | Disables the pre-proxy for this profile, regardless of the global setting. |
This setting can be changed via a quick-switch dropdown on the main profile list or in the detailed profile edit modal.
Sources: renderer.js:560-564, 664, index.html:610-614, 1045-1049
Operational Modes
The system supports three modes for selecting a pre-proxy node from the available list when a profile is launched.
| Mode | Description |
|---|---|
Single Node (single) | A specific proxy node is manually selected from the list and used for all connections. |
Load Balance (balance) | A random proxy node is selected from the list of enabled proxies each time a profile is launched. |
Failover (failover) | The first enabled proxy node from the list is always used. (Note: The current implementation in main.js picks the first active proxy, implying a simple primary-only failover). |
Sources: renderer.js:1033, 30-32, main.js:315-320
Proxy Node Management
Proxy nodes for the chain can be added manually or imported automatically from remote subscriptions. These are managed within the "Manage Chain" modal, which separates nodes into groups based on their source (manual or a specific subscription).
Sources: renderer.js:713-724, 825-839
Manual Addition and Subscriptions
Users can add individual proxy nodes by providing a remark and a valid proxy link (e.g., vmess://, ss://). Alternatively, they can add a subscription URL. The application can parse Base64-encoded subscription content to extract a list of proxy nodes. Subscriptions can be configured to auto-update at set intervals (e.g., 24 or 72 hours).
The data structure for a manually added or subscription-imported proxy node is as follows:
| Field | Type | Description |
|---|---|---|
id | string | A unique identifier for the proxy node. |
remark | string | A user-friendly name for the node. |
url | string | The full proxy connection string. |
enable | boolean | Whether the node is active for selection in balance or failover modes. |
groupId | string | Identifier linking the node to its source ('manual' or a subscription ID). |
latency | number | The last measured latency in milliseconds. -1 indicates a failed test. |
Sources: renderer.js:862-870, 930-944
Latency Testing
The UI provides functionality to test the latency of individual proxy nodes or all nodes within the current group. The test-proxy-latency IPC event is invoked, which spins up a temporary Xray instance to measure the connection time to http://cp.cloudflare.com/generate_204. The result is stored in the latency property of the proxy object and displayed in the UI.
This diagram shows the sequence for testing a single proxy's latency.
Sources: renderer.js:964-972, main.js:122-143
Integration with Profile Launch
The proxy chaining logic is executed when a user clicks the "Launch" button for a profile. The launch-profile IPC handler in main.js orchestrates the entire process.
The sequence of events is as follows:
- The handler receives the
profileId. - It loads the global
settings.jsonand the specific profile's data. - It determines if a pre-proxy should be used based on the profile's
preProxyOverrideand the globalenablePreProxysetting. - If a pre-proxy is needed, it selects a node from
settings.preProxiesaccording to the configuredmode(single,balance, orfailover). - It calls
generateXrayConfig()fromutils.js, passing the main profile's proxy and the selected pre-proxy configuration. generateXrayConfig()creates an Xray configuration where the main proxy is chained to the pre-proxy.- The Xray process is spawned using this configuration.
- The Puppeteer browser instance is launched with the
--proxy-serverargument pointing to the local SOCKS5 port managed by Xray.
Sources: main.js:283-453
Xray Configuration Generation
The core of the chaining mechanism lies in the generateXrayConfig function. When a preProxyConfig is provided, it first parses the pre-proxy URL into a standard Xray outbound object and gives it the tag proxy_pre. It then sets the proxySettings property on the main proxy's outbound object (proxy_main) to use proxy_pre as its exit point.
// Source: utils.js:283-305
function generateXrayConfig(mainProxyStr, localPort, preProxyConfig = null) {
const outbounds = [];
let mainOutbound;
try { mainOutbound = parseProxyLink(mainProxyStr, "proxy_main"); }
catch (e) { mainOutbound = { protocol: "freedom", tag: "proxy_main" }; }
if (preProxyConfig && preProxyConfig.preProxies && preProxyConfig.preProxies.length > 0) {
try {
const target = preProxyConfig.preProxies[0];
const preOutbound = parseProxyLink(target.url, "proxy_pre");
outbounds.push(preOutbound);
mainOutbound.proxySettings = { tag: "proxy_pre" }; // This line creates the chain
} catch (e) { }
}
outbounds.push(mainOutbound);
outbounds.push({ protocol: "freedom", tag: "direct" });
return {
// ... inbounds and routing rules
};
}
Sources: utils.js:283-305