mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-29 23:27:51 +02:00
style: update ui to accept proxy separately
This commit is contained in:
+41
-13
@@ -11,10 +11,15 @@ import { runProxyWorker } from "./proxy-worker";
|
||||
program
|
||||
.command("proxy")
|
||||
.argument("<action>", "start, stop, or list proxies")
|
||||
.option("-h, --host <host>", "upstream proxy host")
|
||||
.option("-P, --proxy-port <port>", "upstream proxy port", Number.parseInt)
|
||||
.option(
|
||||
"-u, --upstream <url>",
|
||||
"upstream proxy URL (protocol://[username:password@]host:port)"
|
||||
"-t, --type <type>",
|
||||
"upstream proxy type (http, https, socks4, socks5)",
|
||||
"http"
|
||||
)
|
||||
.option("-u, --username <username>", "upstream proxy username")
|
||||
.option("-w, --password <password>", "upstream proxy password")
|
||||
.option(
|
||||
"-p, --port <number>",
|
||||
"local port to use (random if not specified)",
|
||||
@@ -27,23 +32,35 @@ program
|
||||
async (
|
||||
action: string,
|
||||
options: {
|
||||
upstream?: string;
|
||||
host?: string;
|
||||
proxyPort?: number;
|
||||
type?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
port?: number;
|
||||
ignoreCertificate?: boolean;
|
||||
id?: string;
|
||||
}
|
||||
) => {
|
||||
if (action === "start") {
|
||||
if (!options.upstream) {
|
||||
console.error("Error: Upstream proxy URL is required");
|
||||
if (!options.host || !options.proxyPort) {
|
||||
console.error("Error: Upstream proxy host and port are required");
|
||||
console.log(
|
||||
"Example: proxy start -u http://username:password@proxy.example.com:8080"
|
||||
"Example: proxy start -h proxy.example.com -P 8080 -t http -u username -w password"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const config = await startProxyProcess(options.upstream, {
|
||||
// Construct the upstream URL with credentials if provided
|
||||
let upstreamProxyUrl: string;
|
||||
if (options.username && options.password) {
|
||||
upstreamProxyUrl = `${options.type}://${options.username}:${options.password}@${options.host}:${options.proxyPort}`;
|
||||
} else {
|
||||
upstreamProxyUrl = `${options.type}://${options.host}:${options.proxyPort}`;
|
||||
}
|
||||
|
||||
const config = await startProxyProcess(upstreamProxyUrl, {
|
||||
port: options.port,
|
||||
ignoreProxyCertificate: options.ignoreCertificate,
|
||||
});
|
||||
@@ -56,14 +73,25 @@ program
|
||||
const stopped = await stopProxyProcess(options.id);
|
||||
console.log(`{
|
||||
"success": ${stopped}}`);
|
||||
} else if (options.upstream) {
|
||||
// Find proxies with this upstream URL
|
||||
const configs = listProxyConfigs().filter(
|
||||
(config) => config.upstreamUrl === options.upstream
|
||||
);
|
||||
} else if (options.host && options.proxyPort && options.type) {
|
||||
// Find proxies with matching upstream details
|
||||
const configs = listProxyConfigs().filter((config) => {
|
||||
try {
|
||||
const url = new URL(config.upstreamUrl);
|
||||
return (
|
||||
url.hostname === options.host &&
|
||||
Number.parseInt(url.port) === options.proxyPort &&
|
||||
url.protocol.replace(":", "") === options.type
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (configs.length === 0) {
|
||||
console.error(`No proxies found for ${options.upstream}`);
|
||||
console.error(
|
||||
`No proxies found for ${options.host}:${options.proxyPort}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+49
-19
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
startProxyProcess,
|
||||
stopProxyProcess,
|
||||
stopAllProxyProcesses
|
||||
import {
|
||||
startProxyProcess,
|
||||
stopProxyProcess,
|
||||
stopAllProxyProcesses,
|
||||
} from "./proxy-runner";
|
||||
import { listProxyConfigs } from "./proxy-storage";
|
||||
|
||||
@@ -9,41 +9,71 @@ import { listProxyConfigs } from "./proxy-storage";
|
||||
interface ProxyOptions {
|
||||
port?: number;
|
||||
ignoreProxyCertificate?: boolean;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a local proxy server that forwards to an upstream proxy
|
||||
* @param upstreamProxyUrl The upstream proxy URL (protocol://[username:password@]host:port)
|
||||
* @param options Optional configuration
|
||||
* @param upstreamProxyHost The upstream proxy host
|
||||
* @param upstreamProxyPort The upstream proxy port
|
||||
* @param upstreamProxyType The upstream proxy type (http, https, socks4, socks5)
|
||||
* @param options Optional configuration including credentials
|
||||
* @returns Promise resolving to the local proxy URL
|
||||
*/
|
||||
export async function startProxy(
|
||||
upstreamProxyUrl: string,
|
||||
upstreamProxyHost: string,
|
||||
upstreamProxyPort: number,
|
||||
upstreamProxyType: string,
|
||||
options: ProxyOptions = {}
|
||||
): Promise<string> {
|
||||
// Construct the upstream proxy URL with credentials if provided
|
||||
let upstreamProxyUrl: string;
|
||||
if (options.username && options.password) {
|
||||
upstreamProxyUrl = `${upstreamProxyType}://${options.username}:${options.password}@${upstreamProxyHost}:${upstreamProxyPort}`;
|
||||
} else {
|
||||
upstreamProxyUrl = `${upstreamProxyType}://${upstreamProxyHost}:${upstreamProxyPort}`;
|
||||
}
|
||||
|
||||
const config = await startProxyProcess(upstreamProxyUrl, {
|
||||
port: options.port,
|
||||
ignoreProxyCertificate: options.ignoreProxyCertificate,
|
||||
});
|
||||
|
||||
|
||||
return config.localUrl || `http://localhost:${config.localPort}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a specific proxy by its upstream URL
|
||||
* @param upstreamProxyUrl The upstream proxy URL to stop
|
||||
* Stop a specific proxy by its upstream host, port, and type
|
||||
* @param upstreamProxyHost The upstream proxy host
|
||||
* @param upstreamProxyPort The upstream proxy port
|
||||
* @param upstreamProxyType The upstream proxy type
|
||||
* @returns Promise resolving to true if proxy was found and stopped, false otherwise
|
||||
*/
|
||||
export async function stopProxy(upstreamProxyUrl: string): Promise<boolean> {
|
||||
// Find all proxies with this upstream URL
|
||||
const configs = listProxyConfigs().filter(
|
||||
config => config.upstreamUrl === upstreamProxyUrl
|
||||
);
|
||||
|
||||
export async function stopProxy(
|
||||
upstreamProxyHost: string,
|
||||
upstreamProxyPort: number,
|
||||
upstreamProxyType: string
|
||||
): Promise<boolean> {
|
||||
// Find all proxies with matching upstream details (ignoring credentials in URL)
|
||||
const configs = listProxyConfigs().filter((config) => {
|
||||
// Parse the upstream URL to extract host, port, and type
|
||||
try {
|
||||
const url = new URL(config.upstreamUrl);
|
||||
return (
|
||||
url.hostname === upstreamProxyHost &&
|
||||
Number.parseInt(url.port) === upstreamProxyPort &&
|
||||
url.protocol.replace(":", "") === upstreamProxyType
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (configs.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Stop all matching proxies
|
||||
let success = true;
|
||||
for (const config of configs) {
|
||||
@@ -52,7 +82,7 @@ export async function stopProxy(upstreamProxyUrl: string): Promise<boolean> {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -61,7 +91,7 @@ export async function stopProxy(upstreamProxyUrl: string): Promise<boolean> {
|
||||
* @returns Array of upstream proxy URLs
|
||||
*/
|
||||
export function getActiveProxies(): string[] {
|
||||
return listProxyConfigs().map(config => config.upstreamUrl);
|
||||
return listProxyConfigs().map((config) => config.upstreamUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,8 @@ pub struct ProxySettings {
|
||||
pub proxy_type: String, // "http", "https", "socks4", or "socks5"
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub username: Option<String>,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
@@ -860,6 +862,8 @@ mod tests {
|
||||
proxy_type: "http".to_string(),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 8080,
|
||||
username: None,
|
||||
password: None,
|
||||
};
|
||||
|
||||
assert!(proxy.enabled);
|
||||
@@ -873,6 +877,8 @@ mod tests {
|
||||
proxy_type: "socks5".to_string(),
|
||||
host: "proxy.example.com".to_string(),
|
||||
port: 1080,
|
||||
username: None,
|
||||
password: None,
|
||||
};
|
||||
|
||||
assert_eq!(socks_proxy.proxy_type, "socks5");
|
||||
@@ -949,6 +955,8 @@ mod tests {
|
||||
proxy_type: "http".to_string(),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 8080,
|
||||
username: None,
|
||||
password: None,
|
||||
};
|
||||
|
||||
// Test that it can be serialized (implements Serialize)
|
||||
|
||||
@@ -1988,14 +1988,13 @@ impl BrowserRunner {
|
||||
|
||||
if !proxy_active {
|
||||
// Browser is running but proxy is not - restart the proxy
|
||||
if let Some((upstream_url, _preferred_port)) =
|
||||
PROXY_MANAGER.get_profile_proxy_info(&inner_profile.name)
|
||||
if let Some(proxy_settings) = PROXY_MANAGER.get_profile_proxy_info(&inner_profile.name)
|
||||
{
|
||||
// Restart the proxy with the same configuration
|
||||
match PROXY_MANAGER
|
||||
.start_proxy(
|
||||
app_handle,
|
||||
&upstream_url,
|
||||
&proxy_settings,
|
||||
inner_profile.process_id.unwrap(),
|
||||
Some(&inner_profile.name),
|
||||
)
|
||||
@@ -2171,12 +2170,9 @@ pub async fn launch_browser_profile(
|
||||
if proxy.enabled {
|
||||
// Get the process ID
|
||||
if let Some(pid) = updated_profile.process_id {
|
||||
// Start a proxy for the upstream URL
|
||||
let upstream_url = format!("{}://{}:{}", proxy.proxy_type, proxy.host, proxy.port);
|
||||
|
||||
// Start the proxy
|
||||
match PROXY_MANAGER
|
||||
.start_proxy(app_handle.clone(), &upstream_url, pid, Some(&profile.name))
|
||||
.start_proxy(app_handle.clone(), proxy, pid, Some(&profile.name))
|
||||
.await
|
||||
{
|
||||
Ok(internal_proxy_settings) => {
|
||||
@@ -2628,6 +2624,8 @@ mod tests {
|
||||
proxy_type: "http".to_string(),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 8080,
|
||||
username: None,
|
||||
password: None,
|
||||
};
|
||||
|
||||
let profile = runner
|
||||
@@ -2683,6 +2681,8 @@ mod tests {
|
||||
proxy_type: "socks5".to_string(),
|
||||
host: "192.168.1.1".to_string(),
|
||||
port: 1080,
|
||||
username: None,
|
||||
password: None,
|
||||
};
|
||||
|
||||
let updated_profile = runner
|
||||
@@ -2838,6 +2838,8 @@ mod tests {
|
||||
proxy_type: "http".to_string(),
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 8080,
|
||||
username: None,
|
||||
password: None,
|
||||
};
|
||||
|
||||
let profile_with_proxy = runner
|
||||
|
||||
@@ -11,7 +11,9 @@ use crate::browser::ProxySettings;
|
||||
pub struct ProxyInfo {
|
||||
pub id: String,
|
||||
pub local_url: String,
|
||||
pub upstream_url: String,
|
||||
pub upstream_host: String,
|
||||
pub upstream_port: u16,
|
||||
pub upstream_type: String,
|
||||
pub local_port: u16,
|
||||
}
|
||||
|
||||
@@ -19,7 +21,7 @@ pub struct ProxyInfo {
|
||||
pub struct ProxyManager {
|
||||
active_proxies: Mutex<HashMap<u32, ProxyInfo>>, // Maps browser process ID to proxy info
|
||||
// Store proxy info by profile name for persistence across browser restarts
|
||||
profile_proxies: Mutex<HashMap<String, (String, u16)>>, // Maps profile name to (upstream_url, port)
|
||||
profile_proxies: Mutex<HashMap<String, ProxySettings>>, // Maps profile name to proxy settings
|
||||
}
|
||||
|
||||
impl ProxyManager {
|
||||
@@ -30,11 +32,11 @@ impl ProxyManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Start a proxy for a given upstream URL and associate it with a browser process ID
|
||||
// Start a proxy for given proxy settings and associate it with a browser process ID
|
||||
pub async fn start_proxy(
|
||||
&self,
|
||||
app_handle: tauri::AppHandle,
|
||||
upstream_url: &str,
|
||||
proxy_settings: &ProxySettings,
|
||||
browser_pid: u32,
|
||||
profile_name: Option<&str>,
|
||||
) -> Result<ProxySettings, String> {
|
||||
@@ -44,9 +46,11 @@ impl ProxyManager {
|
||||
if let Some(proxy) = proxies.get(&browser_pid) {
|
||||
return Ok(ProxySettings {
|
||||
enabled: true,
|
||||
proxy_type: "http".to_string(),
|
||||
proxy_type: proxy.upstream_type.clone(),
|
||||
host: "localhost".to_string(),
|
||||
port: proxy.local_port,
|
||||
username: None,
|
||||
password: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -54,7 +58,18 @@ impl ProxyManager {
|
||||
// Check if we have a preferred port for this profile
|
||||
let preferred_port = if let Some(name) = profile_name {
|
||||
let profile_proxies = self.profile_proxies.lock().unwrap();
|
||||
profile_proxies.get(name).map(|(_, port)| *port)
|
||||
profile_proxies.get(name).and_then(|settings| {
|
||||
// Find existing proxy with same settings to reuse port
|
||||
let active_proxies = self.active_proxies.lock().unwrap();
|
||||
active_proxies
|
||||
.values()
|
||||
.find(|p| {
|
||||
p.upstream_host == settings.host
|
||||
&& p.upstream_port == settings.port
|
||||
&& p.upstream_type == settings.proxy_type
|
||||
})
|
||||
.map(|p| p.local_port)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -66,8 +81,20 @@ impl ProxyManager {
|
||||
.unwrap()
|
||||
.arg("proxy")
|
||||
.arg("start")
|
||||
.arg("-u")
|
||||
.arg(upstream_url);
|
||||
.arg("-h")
|
||||
.arg(&proxy_settings.host)
|
||||
.arg("-P")
|
||||
.arg(proxy_settings.port.to_string())
|
||||
.arg("-t")
|
||||
.arg(&proxy_settings.proxy_type);
|
||||
|
||||
// Add credentials if provided
|
||||
if let Some(username) = &proxy_settings.username {
|
||||
nodecar = nodecar.arg("-u").arg(username);
|
||||
}
|
||||
if let Some(password) = &proxy_settings.password {
|
||||
nodecar = nodecar.arg("-w").arg(password);
|
||||
}
|
||||
|
||||
// If we have a preferred port, use it
|
||||
if let Some(port) = preferred_port {
|
||||
@@ -95,15 +122,13 @@ impl ProxyManager {
|
||||
.as_str()
|
||||
.ok_or("Missing local URL")?
|
||||
.to_string();
|
||||
let upstream_url_str = json["upstreamUrl"]
|
||||
.as_str()
|
||||
.ok_or("Missing upstream URL")?
|
||||
.to_string();
|
||||
|
||||
let proxy_info = ProxyInfo {
|
||||
id: id.to_string(),
|
||||
local_url,
|
||||
upstream_url: upstream_url_str.clone(),
|
||||
upstream_host: proxy_settings.host.clone(),
|
||||
upstream_port: proxy_settings.port,
|
||||
upstream_type: proxy_settings.proxy_type.clone(),
|
||||
local_port,
|
||||
};
|
||||
|
||||
@@ -116,7 +141,7 @@ impl ProxyManager {
|
||||
// Store the profile proxy info for persistence
|
||||
if let Some(name) = profile_name {
|
||||
let mut profile_proxies = self.profile_proxies.lock().unwrap();
|
||||
profile_proxies.insert(name.to_string(), (upstream_url_str, local_port));
|
||||
profile_proxies.insert(name.to_string(), proxy_settings.clone());
|
||||
}
|
||||
|
||||
// Return proxy settings for the browser
|
||||
@@ -125,6 +150,8 @@ impl ProxyManager {
|
||||
proxy_type: "http".to_string(),
|
||||
host: "localhost".to_string(),
|
||||
port: proxy_info.local_port,
|
||||
username: None,
|
||||
password: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -171,11 +198,13 @@ impl ProxyManager {
|
||||
proxy_type: "http".to_string(),
|
||||
host: "localhost".to_string(),
|
||||
port: proxy.local_port,
|
||||
username: None,
|
||||
password: None,
|
||||
})
|
||||
}
|
||||
|
||||
// Get stored proxy info for a profile
|
||||
pub fn get_profile_proxy_info(&self, profile_name: &str) -> Option<(String, u16)> {
|
||||
pub fn get_profile_proxy_info(&self, profile_name: &str) -> Option<ProxySettings> {
|
||||
let profile_proxies = self.profile_proxies.lock().unwrap();
|
||||
profile_proxies.get(profile_name).cloned()
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ interface ProxySettings {
|
||||
proxy_type: string;
|
||||
host: string;
|
||||
port: number;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
interface ProxySettingsDialogProps {
|
||||
@@ -52,6 +54,8 @@ export function ProxySettingsDialog({
|
||||
proxy_type: initialSettings?.proxy_type ?? "http",
|
||||
host: initialSettings?.host ?? "",
|
||||
port: initialSettings?.port ?? 8080,
|
||||
username: initialSettings?.username ?? "",
|
||||
password: initialSettings?.password ?? "",
|
||||
});
|
||||
|
||||
const [initialSettingsState, setInitialSettingsState] =
|
||||
@@ -60,6 +64,8 @@ export function ProxySettingsDialog({
|
||||
proxy_type: "http",
|
||||
host: "",
|
||||
port: 8080,
|
||||
username: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -69,6 +75,8 @@ export function ProxySettingsDialog({
|
||||
proxy_type: initialSettings.proxy_type,
|
||||
host: initialSettings.host,
|
||||
port: initialSettings.port,
|
||||
username: initialSettings.username ?? "",
|
||||
password: initialSettings.password ?? "",
|
||||
};
|
||||
setSettings(newSettings);
|
||||
setInitialSettingsState(newSettings);
|
||||
@@ -78,6 +86,8 @@ export function ProxySettingsDialog({
|
||||
proxy_type: "http",
|
||||
host: "",
|
||||
port: 80,
|
||||
username: "",
|
||||
password: "",
|
||||
};
|
||||
setSettings(defaultSettings);
|
||||
setInitialSettingsState(defaultSettings);
|
||||
@@ -94,7 +104,9 @@ export function ProxySettingsDialog({
|
||||
settings.enabled !== initialSettingsState.enabled ||
|
||||
settings.proxy_type !== initialSettingsState.proxy_type ||
|
||||
settings.host !== initialSettingsState.host ||
|
||||
settings.port !== initialSettingsState.port
|
||||
settings.port !== initialSettingsState.port ||
|
||||
settings.username !== initialSettingsState.username ||
|
||||
settings.password !== initialSettingsState.password
|
||||
);
|
||||
};
|
||||
|
||||
@@ -214,6 +226,31 @@ export function ProxySettingsDialog({
|
||||
max="65535"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="username">Username (optional)</Label>
|
||||
<Input
|
||||
id="username"
|
||||
value={settings.username}
|
||||
onChange={(e) => {
|
||||
setSettings({ ...settings, username: e.target.value });
|
||||
}}
|
||||
placeholder="Proxy username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="password">Password (optional)</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={settings.password}
|
||||
onChange={(e) => {
|
||||
setSettings({ ...settings, password: e.target.value });
|
||||
}}
|
||||
placeholder="Proxy password"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,8 @@ export interface ProxySettings {
|
||||
proxy_type: string; // "http", "https", "socks4", or "socks5"
|
||||
host: string;
|
||||
port: number;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface TableSortingSettings {
|
||||
|
||||
Reference in New Issue
Block a user