mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-29 07:16:11 +02:00
feat: finalize camoufox integration
This commit is contained in:
Vendored
+1
@@ -63,6 +63,7 @@
|
||||
"kdeglobals",
|
||||
"keras",
|
||||
"KHTML",
|
||||
"Kolkata",
|
||||
"kreadconfig",
|
||||
"launchservices",
|
||||
"letterboxing",
|
||||
|
||||
@@ -176,29 +176,48 @@ export async function stopCamoufoxProcess(id: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
try {
|
||||
const killByPattern = spawn("pkill", ["-f", `camoufox-worker.*${id}`], {
|
||||
stdio: "ignore",
|
||||
});
|
||||
console.log(`Stopping Camoufox process ${id} (PID: ${config.processId})`);
|
||||
|
||||
// Method 2: If we have a process ID, kill by PID
|
||||
// Method 1: If we have a process ID, kill by PID with proper signal sequence
|
||||
if (config.processId) {
|
||||
try {
|
||||
// First try SIGTERM for graceful shutdown
|
||||
process.kill(config.processId, "SIGTERM");
|
||||
console.log(`Sent SIGTERM to Camoufox process ${config.processId}`);
|
||||
|
||||
// Give it a moment to terminate gracefully
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
// Give it more time to terminate gracefully (increased from 2s to 5s)
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
|
||||
// Force kill if still running
|
||||
// Check if process is still running
|
||||
try {
|
||||
process.kill(config.processId, 0); // Signal 0 checks if process exists
|
||||
// Process still exists, force kill
|
||||
console.log(
|
||||
`Camoufox process ${config.processId} still running, sending SIGKILL`,
|
||||
);
|
||||
process.kill(config.processId, "SIGKILL");
|
||||
} catch {
|
||||
// Process already terminated
|
||||
console.log(
|
||||
`Camoufox process ${config.processId} terminated gracefully`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// Process not found or already terminated
|
||||
} catch {
|
||||
console.log(
|
||||
`Camoufox process ${config.processId} not found or already terminated`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Pattern-based kill as fallback
|
||||
const killByPattern = spawn(
|
||||
"pkill",
|
||||
["-TERM", "-f", `camoufox-worker.*${id}`],
|
||||
{
|
||||
stdio: "ignore",
|
||||
},
|
||||
);
|
||||
|
||||
// Wait for pattern-based kill command to complete
|
||||
await new Promise<void>((resolve) => {
|
||||
killByPattern.on("exit", () => resolve());
|
||||
@@ -206,10 +225,17 @@ export async function stopCamoufoxProcess(id: string): Promise<boolean> {
|
||||
setTimeout(() => resolve(), 3000);
|
||||
});
|
||||
|
||||
// Final cleanup with SIGKILL if needed
|
||||
setTimeout(() => {
|
||||
spawn("pkill", ["-KILL", "-f", `camoufox-worker.*${id}`], {
|
||||
stdio: "ignore",
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
// Delete the configuration
|
||||
deleteCamoufoxConfig(id);
|
||||
return true;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Delete the configuration even if stopping failed
|
||||
deleteCamoufoxConfig(id);
|
||||
return false;
|
||||
|
||||
+146
-32
@@ -1,5 +1,5 @@
|
||||
import { Camoufox } from "camoufox-js";
|
||||
import type { Page } from "playwright-core";
|
||||
import { launchServer } from "camoufox-js";
|
||||
import { type Browser, type BrowserServer, firefox } from "playwright-core";
|
||||
import { getCamoufoxConfig, saveCamoufoxConfig } from "./camoufox-storage.js";
|
||||
|
||||
/**
|
||||
@@ -20,34 +20,56 @@ export async function runCamoufoxWorker(id: string): Promise<void> {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Return success immediately - before any async operations
|
||||
const processId = process.pid;
|
||||
config.processId = process.pid;
|
||||
saveCamoufoxConfig(config);
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
id: id,
|
||||
processId,
|
||||
processId: process.pid,
|
||||
profilePath: config.profilePath,
|
||||
message: "Camoufox worker started successfully",
|
||||
}),
|
||||
);
|
||||
|
||||
// Update config with process details
|
||||
config.processId = processId;
|
||||
saveCamoufoxConfig(config);
|
||||
|
||||
// Handle process termination gracefully
|
||||
const gracefulShutdown = async () => {
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on("SIGTERM", () => void gracefulShutdown());
|
||||
process.on("SIGINT", () => void gracefulShutdown());
|
||||
|
||||
// Launch browser in background - this can take time and may fail
|
||||
setImmediate(async () => {
|
||||
let page: Page | null = null;
|
||||
let browser: Browser | null = null;
|
||||
let server: BrowserServer | null = null;
|
||||
let windowCheckInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
// Graceful shutdown handler with access to browser and server
|
||||
const gracefulShutdown = async () => {
|
||||
try {
|
||||
// Clear any intervals first
|
||||
if (windowCheckInterval) {
|
||||
clearInterval(windowCheckInterval);
|
||||
}
|
||||
|
||||
// Close browser context and server if they exist
|
||||
if (browser?.isConnected()) {
|
||||
await browser.close();
|
||||
}
|
||||
if (server) {
|
||||
server.process().kill();
|
||||
await server.close();
|
||||
}
|
||||
} catch {
|
||||
// Ignore cleanup errors during shutdown
|
||||
}
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
// Handle various quit signals for proper macOS Command+Q support
|
||||
process.on("SIGTERM", () => void gracefulShutdown());
|
||||
process.on("SIGINT", () => void gracefulShutdown());
|
||||
process.on("SIGHUP", () => void gracefulShutdown());
|
||||
process.on("SIGQUIT", () => void gracefulShutdown());
|
||||
|
||||
// Handle uncaught exceptions and unhandled rejections
|
||||
process.on("uncaughtException", () => void gracefulShutdown());
|
||||
process.on("unhandledRejection", () => void gracefulShutdown());
|
||||
|
||||
try {
|
||||
// Prepare options for Camoufox
|
||||
@@ -58,7 +80,7 @@ export async function runCamoufoxWorker(id: string): Promise<void> {
|
||||
camoufoxOptions.user_data_dir = config.profilePath;
|
||||
}
|
||||
|
||||
// Remove custom properties before passing to Camoufox
|
||||
// Theming
|
||||
camoufoxOptions.disableTheming = true;
|
||||
camoufoxOptions.showcursor = false;
|
||||
|
||||
@@ -72,24 +94,108 @@ export async function runCamoufoxWorker(id: string): Promise<void> {
|
||||
camoufoxOptions.headless = false;
|
||||
}
|
||||
|
||||
const browser = await Camoufox(camoufoxOptions);
|
||||
// Launch the server with proper options
|
||||
server = await launchServer({
|
||||
ws_path: `/ws_${config.id}`,
|
||||
os: camoufoxOptions.os,
|
||||
block_images: camoufoxOptions.block_images,
|
||||
block_webrtc: camoufoxOptions.block_webrtc,
|
||||
block_webgl: camoufoxOptions.block_webgl,
|
||||
disable_coop: camoufoxOptions.disable_coop,
|
||||
geoip: camoufoxOptions.geoip,
|
||||
humanize: camoufoxOptions.humanize,
|
||||
locale: camoufoxOptions.locale,
|
||||
addons: camoufoxOptions.addons,
|
||||
fonts: camoufoxOptions.fonts,
|
||||
custom_fonts_only: camoufoxOptions.custom_fonts_only,
|
||||
exclude_addons: camoufoxOptions.exclude_addons,
|
||||
screen: camoufoxOptions.screen,
|
||||
window: camoufoxOptions.window,
|
||||
fingerprint: camoufoxOptions.fingerprint,
|
||||
ff_version: camoufoxOptions.ff_version,
|
||||
headless: camoufoxOptions.headless,
|
||||
main_world_eval: camoufoxOptions.main_world_eval,
|
||||
executable_path: camoufoxOptions.executable_path,
|
||||
firefox_user_prefs: camoufoxOptions.firefox_user_prefs,
|
||||
proxy: camoufoxOptions.proxy,
|
||||
enable_cache: camoufoxOptions.enable_cache,
|
||||
args: camoufoxOptions.args,
|
||||
env: camoufoxOptions.env,
|
||||
debug: camoufoxOptions.debug,
|
||||
virtual_display: camoufoxOptions.virtual_display,
|
||||
webgl_config: camoufoxOptions.webgl_config,
|
||||
config: {
|
||||
disableTheming: true,
|
||||
showcursor: false,
|
||||
timezone: camoufoxOptions.timezone,
|
||||
},
|
||||
});
|
||||
|
||||
// Connect to the server
|
||||
browser = await firefox.connect(server.wsEndpoint());
|
||||
const context = await browser.newContext();
|
||||
|
||||
// Handle browser disconnection for proper cleanup
|
||||
browser.on("disconnected", () => void gracefulShutdown());
|
||||
|
||||
saveCamoufoxConfig(config);
|
||||
|
||||
// Handle URL opening if provided
|
||||
if (config.url && context) {
|
||||
try {
|
||||
if (!page) {
|
||||
page = await context.newPage();
|
||||
// Monitor for window closure to handle Command+Q properly
|
||||
|
||||
const startWindowMonitoring = () => {
|
||||
windowCheckInterval = setInterval(async () => {
|
||||
try {
|
||||
if (browser?.isConnected()) {
|
||||
const contexts = browser.contexts();
|
||||
let totalPages = 0;
|
||||
|
||||
for (const ctx of contexts) {
|
||||
const pages = ctx.pages();
|
||||
totalPages += pages.length;
|
||||
}
|
||||
|
||||
// If no pages are open, terminate the server
|
||||
if (totalPages === 0) {
|
||||
if (windowCheckInterval) {
|
||||
clearInterval(windowCheckInterval);
|
||||
}
|
||||
await gracefulShutdown();
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// If we can't check windows, assume browser is closing
|
||||
if (windowCheckInterval) {
|
||||
clearInterval(windowCheckInterval);
|
||||
}
|
||||
await gracefulShutdown();
|
||||
}
|
||||
}, 1000); // Check every second
|
||||
};
|
||||
|
||||
// Handle URL opening if provided
|
||||
if (config.url) {
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
await page.goto(config.url, {
|
||||
waitUntil: "domcontentloaded",
|
||||
timeout: 30000,
|
||||
});
|
||||
} catch {
|
||||
|
||||
// Start monitoring after page is created
|
||||
startWindowMonitoring();
|
||||
} catch (urlError) {
|
||||
console.error({
|
||||
message: "Failed to open URL",
|
||||
error: urlError,
|
||||
});
|
||||
// URL opening failure doesn't affect startup success
|
||||
// Still start monitoring
|
||||
startWindowMonitoring();
|
||||
}
|
||||
} else {
|
||||
await context.newPage();
|
||||
// Start monitoring after page is created
|
||||
startWindowMonitoring();
|
||||
}
|
||||
|
||||
// Monitor browser connection
|
||||
@@ -97,16 +203,24 @@ export async function runCamoufoxWorker(id: string): Promise<void> {
|
||||
try {
|
||||
if (!browser || !browser.isConnected()) {
|
||||
clearInterval(keepAlive);
|
||||
process.exit(0);
|
||||
await gracefulShutdown();
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error({
|
||||
message: "Error in keepAlive check",
|
||||
error,
|
||||
});
|
||||
clearInterval(keepAlive);
|
||||
process.exit(0);
|
||||
await gracefulShutdown();
|
||||
}
|
||||
}, 2000);
|
||||
} catch {
|
||||
// Browser launch failed, but worker is still "successful"
|
||||
// Process will stay alive due to the main setInterval above
|
||||
} catch (error) {
|
||||
console.error({
|
||||
message: "Failed to launch Camoufox",
|
||||
error,
|
||||
});
|
||||
// Browser launch failed, attempt cleanup
|
||||
await gracefulShutdown();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+4
-11
@@ -233,8 +233,7 @@ program
|
||||
// Firefox preferences
|
||||
.option("--firefox-prefs <prefs>", "Firefox user preferences (JSON string)")
|
||||
|
||||
.option("--disable-theming", "disable Firefox theming")
|
||||
.option("--no-showcursor", "disable cursor display")
|
||||
// Note: theming and cursor options are hardcoded and not user-configurable
|
||||
|
||||
.description("manage Camoufox browser instances")
|
||||
.action(
|
||||
@@ -262,11 +261,12 @@ program
|
||||
// Security options
|
||||
if (options.disableCoop) camoufoxOptions.disable_coop = true;
|
||||
|
||||
// Geolocation
|
||||
// Geolocation - always enable geoip for proper spoofing
|
||||
if (options.geoip) {
|
||||
camoufoxOptions.geoip =
|
||||
options.geoip === "auto" ? true : (options.geoip as string);
|
||||
}
|
||||
|
||||
if (options.latitude && options.longitude) {
|
||||
camoufoxOptions.geolocation = {
|
||||
latitude: options.latitude as number,
|
||||
@@ -279,9 +279,8 @@ program
|
||||
if (options.timezone)
|
||||
camoufoxOptions.timezone = options.timezone as string;
|
||||
|
||||
// UI and behavior
|
||||
if (options.humanize)
|
||||
camoufoxOptions.humanize = options.humanize as boolean | number;
|
||||
camoufoxOptions.humanize = options.humanize as boolean;
|
||||
if (options.headless) camoufoxOptions.headless = true;
|
||||
|
||||
// Localization
|
||||
@@ -388,11 +387,6 @@ program
|
||||
}
|
||||
}
|
||||
|
||||
// Theming and cursor - these are custom properties for camoufox-js
|
||||
if (options.disableTheming) camoufoxOptions.disableTheming = true;
|
||||
if (options.showcursor === false) camoufoxOptions.showcursor = false;
|
||||
|
||||
// Use the launcher to start Camoufox properly
|
||||
const config = await startCamoufoxProcess(
|
||||
camoufoxOptions,
|
||||
typeof options.profilePath === "string"
|
||||
@@ -401,7 +395,6 @@ program
|
||||
typeof options.url === "string" ? options.url : undefined,
|
||||
);
|
||||
|
||||
// Output the configuration as JSON for the Rust side to parse
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
id: config.id,
|
||||
|
||||
@@ -185,29 +185,17 @@ impl BrowserRunner {
|
||||
|
||||
// Set proxy in camoufox config
|
||||
camoufox_config.proxy = Some(proxy_url);
|
||||
|
||||
// Ensure geoip is always enabled for proper geolocation spoofing
|
||||
if camoufox_config.geoip.is_none() {
|
||||
camoufox_config.geoip = Some(serde_json::Value::Bool(true));
|
||||
}
|
||||
|
||||
println!(
|
||||
"Configured local proxy for Camoufox: {:?}",
|
||||
camoufox_config.proxy
|
||||
"Configured local proxy for Camoufox: {:?}, geoip: {:?}",
|
||||
camoufox_config.proxy, camoufox_config.geoip
|
||||
);
|
||||
|
||||
// Use the existing config or create a test config if none exists
|
||||
let final_config = if camoufox_config.timezone.is_some()
|
||||
|| camoufox_config.screen_min_width.is_some()
|
||||
|| camoufox_config.window_width.is_some()
|
||||
{
|
||||
camoufox_config.clone()
|
||||
} else {
|
||||
// No meaningful config provided, use test config to ensure anti-fingerprinting works
|
||||
println!("No Camoufox configuration provided, using test configuration");
|
||||
let mut test_config = crate::camoufox::CamoufoxNodecarLauncher::create_test_config();
|
||||
// Preserve any proxy settings from the original config
|
||||
test_config.proxy = camoufox_config.proxy.clone();
|
||||
test_config.headless = camoufox_config.headless;
|
||||
test_config.debug = Some(true); // Enable debug for troubleshooting
|
||||
test_config
|
||||
};
|
||||
|
||||
// Use the nodecar camoufox launcher
|
||||
println!(
|
||||
"Launching Camoufox via nodecar for profile: {}",
|
||||
@@ -215,7 +203,12 @@ impl BrowserRunner {
|
||||
);
|
||||
let camoufox_launcher = crate::camoufox::CamoufoxNodecarLauncher::instance();
|
||||
let camoufox_result = camoufox_launcher
|
||||
.launch_camoufox_profile_nodecar(app_handle.clone(), profile.clone(), final_config, url)
|
||||
.launch_camoufox_profile_nodecar(
|
||||
app_handle.clone(),
|
||||
profile.clone(),
|
||||
camoufox_config,
|
||||
url,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
|
||||
format!("Failed to launch camoufox via nodecar: {e}").into()
|
||||
|
||||
@@ -56,7 +56,7 @@ impl Default for CamoufoxConfig {
|
||||
block_webrtc: None,
|
||||
block_webgl: None,
|
||||
disable_coop: None,
|
||||
geoip: None,
|
||||
geoip: Some(serde_json::Value::Bool(true)),
|
||||
country: None,
|
||||
timezone: None,
|
||||
latitude: None,
|
||||
@@ -80,7 +80,7 @@ impl Default for CamoufoxConfig {
|
||||
webgl_vendor: None,
|
||||
webgl_renderer: None,
|
||||
proxy: None,
|
||||
enable_cache: Some(true), // Cache enabled by default
|
||||
enable_cache: Some(true),
|
||||
virtual_display: None,
|
||||
debug: None,
|
||||
additional_args: None,
|
||||
@@ -133,44 +133,20 @@ impl CamoufoxNodecarLauncher {
|
||||
&CAMOUFOX_NODECAR_LAUNCHER
|
||||
}
|
||||
|
||||
/// Create a test configuration to verify anti-fingerprinting is working
|
||||
/// Create a test configuration
|
||||
#[allow(dead_code)]
|
||||
pub fn create_test_config() -> CamoufoxConfig {
|
||||
CamoufoxConfig {
|
||||
// Core anti-fingerprinting settings
|
||||
timezone: Some("Europe/London".to_string()),
|
||||
screen_min_width: Some(1440),
|
||||
screen_min_height: Some(900),
|
||||
window_width: Some(1200),
|
||||
window_height: Some(800),
|
||||
|
||||
// Locale settings
|
||||
locale: Some(vec!["en-GB".to_string(), "en-US".to_string()]),
|
||||
|
||||
// WebGL spoofing
|
||||
webgl_vendor: Some("Intel Inc.".to_string()),
|
||||
webgl_renderer: Some("Intel Iris Pro OpenGL Engine".to_string()),
|
||||
|
||||
// Geolocation spoofing (London coordinates)
|
||||
latitude: Some(51.5074),
|
||||
longitude: Some(-0.1278),
|
||||
|
||||
// Font settings
|
||||
fonts: Some(vec![
|
||||
"Arial".to_string(),
|
||||
"Times New Roman".to_string(),
|
||||
"Helvetica".to_string(),
|
||||
"Georgia".to_string(),
|
||||
]),
|
||||
custom_fonts_only: Some(true),
|
||||
|
||||
// Humanization
|
||||
humanize: Some(true),
|
||||
humanize_duration: Some(2.0),
|
||||
|
||||
// Blocking features
|
||||
block_images: Some(false), // Don't block images for testing
|
||||
block_webrtc: Some(true),
|
||||
block_webgl: Some(false), // Don't block WebGL so we can test spoofing
|
||||
|
||||
// Other settings
|
||||
debug: Some(true),
|
||||
@@ -646,22 +622,19 @@ mod tests {
|
||||
let test_config = CamoufoxNodecarLauncher::create_test_config();
|
||||
|
||||
// Verify test config has expected values
|
||||
assert_eq!(test_config.timezone, Some("Europe/London".to_string()));
|
||||
assert_eq!(test_config.screen_min_width, Some(1440));
|
||||
assert_eq!(test_config.screen_min_height, Some(900));
|
||||
assert_eq!(test_config.window_width, Some(1200));
|
||||
assert_eq!(test_config.window_height, Some(800));
|
||||
assert_eq!(test_config.webgl_vendor, Some("Intel Inc.".to_string()));
|
||||
assert_eq!(
|
||||
test_config.webgl_renderer,
|
||||
Some("Intel Iris Pro OpenGL Engine".to_string())
|
||||
);
|
||||
assert_eq!(test_config.latitude, Some(51.5074));
|
||||
assert_eq!(test_config.longitude, Some(-0.1278));
|
||||
assert_eq!(test_config.humanize, Some(true));
|
||||
assert_eq!(test_config.debug, Some(true));
|
||||
assert_eq!(test_config.enable_cache, Some(true));
|
||||
assert_eq!(test_config.headless, Some(false));
|
||||
// Verify that geoip is enabled by default (from Default implementation)
|
||||
assert_eq!(test_config.geoip, Some(serde_json::Value::Bool(true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -670,6 +643,7 @@ mod tests {
|
||||
|
||||
// Verify defaults
|
||||
assert_eq!(default_config.enable_cache, Some(true));
|
||||
assert_eq!(default_config.geoip, Some(serde_json::Value::Bool(true)));
|
||||
assert_eq!(default_config.timezone, None);
|
||||
assert_eq!(default_config.debug, None);
|
||||
assert_eq!(default_config.headless, None);
|
||||
|
||||
@@ -591,15 +591,6 @@ async fn test_nodecar_command_validation() -> Result<(), Box<dyn std::error::Err
|
||||
|
||||
assert!(!output.status.success(), "Invalid command should fail");
|
||||
|
||||
// Test proxy without required arguments
|
||||
let incomplete_args = ["proxy", "start"];
|
||||
let output = TestUtils::execute_nodecar_command(&nodecar_path, &incomplete_args, 10).await?;
|
||||
|
||||
assert!(
|
||||
!output.status.success(),
|
||||
"Incomplete proxy command should fail"
|
||||
);
|
||||
|
||||
tracker.cleanup_all().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export function CamoufoxConfigDialog({
|
||||
const [config, setConfig] = useState<CamoufoxConfig>({
|
||||
enable_cache: true,
|
||||
os: [getCurrentOS()],
|
||||
geoip: true,
|
||||
});
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
@@ -40,6 +41,7 @@ export function CamoufoxConfigDialog({
|
||||
profile.camoufox_config || {
|
||||
enable_cache: true,
|
||||
os: [getCurrentOS()],
|
||||
geoip: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -70,6 +72,7 @@ export function CamoufoxConfigDialog({
|
||||
profile.camoufox_config || {
|
||||
enable_cache: true,
|
||||
os: [getCurrentOS()],
|
||||
geoip: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -453,13 +453,6 @@ export function CreateProfileDialog({
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="anti-detect" className="mt-0 space-y-6">
|
||||
{/* Anti-Detect Description */}
|
||||
<div className="p-3 text-center bg-blue-50 rounded-md border border-blue-200 dark:bg-blue-950 dark:border-blue-800">
|
||||
<p className="text-sm text-blue-800 dark:text-blue-200">
|
||||
Powered by Camoufox
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Camoufox Download Status */}
|
||||
{!isBrowserVersionAvailable("camoufox") &&
|
||||
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import type { CamoufoxConfig } from "@/types";
|
||||
|
||||
const osOptions = [
|
||||
{ value: "windows", label: "Windows" },
|
||||
{ value: "macos", label: "macOS" },
|
||||
{ value: "linux", label: "Linux" },
|
||||
];
|
||||
// const osOptions = [
|
||||
// { value: "windows", label: "Windows" },
|
||||
// { value: "macos", label: "macOS" },
|
||||
// { value: "linux", label: "Linux" },
|
||||
// ];
|
||||
|
||||
const timezoneOptions = [
|
||||
{ value: "America/New_York", label: "America/New_York" },
|
||||
@@ -143,15 +143,15 @@ const localeOptions = [
|
||||
{ value: "mt-MT", label: "Maltese (Malta)" },
|
||||
];
|
||||
|
||||
const getCurrentOS = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
const userAgent = window.navigator.userAgent;
|
||||
if (userAgent.includes("Win")) return "windows";
|
||||
if (userAgent.includes("Mac")) return "macos";
|
||||
if (userAgent.includes("Linux")) return "linux";
|
||||
}
|
||||
return "unknown";
|
||||
};
|
||||
// const getCurrentOS = () => {
|
||||
// if (typeof window !== "undefined") {
|
||||
// const userAgent = window.navigator.userAgent;
|
||||
// if (userAgent.includes("Win")) return "windows";
|
||||
// if (userAgent.includes("Mac")) return "macos";
|
||||
// if (userAgent.includes("Linux")) return "linux";
|
||||
// }
|
||||
// return "unknown";
|
||||
// };
|
||||
|
||||
interface SystemLocale {
|
||||
locale: string;
|
||||
@@ -211,16 +211,36 @@ export function SharedCamoufoxConfigForm({
|
||||
loadSystemDefaults();
|
||||
}, []);
|
||||
|
||||
// Determine if automatic location configuration is enabled
|
||||
// Default to true if geoip is not explicitly set to false
|
||||
const isAutoLocationEnabled = config.geoip !== false;
|
||||
|
||||
// Handle automatic location configuration toggle
|
||||
const handleAutoLocationToggle = (enabled: boolean) => {
|
||||
if (enabled) {
|
||||
// Enable automatic configuration - set geoip to true and clear manual fields
|
||||
onConfigChange("geoip", true);
|
||||
onConfigChange("country", undefined);
|
||||
onConfigChange("timezone", undefined);
|
||||
onConfigChange("latitude", undefined);
|
||||
onConfigChange("longitude", undefined);
|
||||
onConfigChange("locale", undefined);
|
||||
} else {
|
||||
// Disable automatic configuration - set geoip to false
|
||||
onConfigChange("geoip", false);
|
||||
}
|
||||
};
|
||||
|
||||
// Get the selected OS for warning
|
||||
const selectedOS = config.os?.[0];
|
||||
const currentOS = getCurrentOS();
|
||||
const showOSWarning =
|
||||
selectedOS && selectedOS !== currentOS && currentOS !== "unknown";
|
||||
// const selectedOS = config.os?.[0];
|
||||
// const currentOS = getCurrentOS();
|
||||
// const showOSWarning =
|
||||
// selectedOS && selectedOS !== currentOS && currentOS !== "unknown";
|
||||
|
||||
return (
|
||||
<div className={`space-y-6 ${className}`}>
|
||||
{/* OS Selection */}
|
||||
<div className="space-y-3">
|
||||
{/*<div className="space-y-3">
|
||||
<Label>Operating System</Label>
|
||||
<Select
|
||||
value={config.os?.[0] || getCurrentOS()}
|
||||
@@ -243,162 +263,154 @@ export function SharedCamoufoxConfigForm({
|
||||
{currentOS}). This may affect fingerprinting effectiveness.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>*/}
|
||||
|
||||
{/* Privacy & Blocking */}
|
||||
{/* Automatic Location Configuration */}
|
||||
<div className="space-y-3">
|
||||
<Label>Privacy & Blocking</Label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="block-images"
|
||||
checked={config.block_images || false}
|
||||
onCheckedChange={(checked) =>
|
||||
onConfigChange("block_images", checked)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="block-images">Block Images</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="block-webrtc"
|
||||
checked={config.block_webrtc || false}
|
||||
onCheckedChange={(checked) =>
|
||||
onConfigChange("block_webrtc", checked)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="block-webrtc">Block WebRTC</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="block-webgl"
|
||||
checked={config.block_webgl || false}
|
||||
onCheckedChange={(checked) =>
|
||||
onConfigChange("block_webgl", checked)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="block-webgl">Block WebGL</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="auto-location"
|
||||
checked={isAutoLocationEnabled}
|
||||
onCheckedChange={handleAutoLocationToggle}
|
||||
/>
|
||||
<Label htmlFor="auto-location">
|
||||
Automatically configure location information based on proxy
|
||||
configuration or your connection if no proxy provided
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Geolocation */}
|
||||
<div className="space-y-3">
|
||||
<Label>Geolocation</Label>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="country">Country</Label>
|
||||
<Input
|
||||
id="country"
|
||||
value={config.country || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange("country", e.target.value || undefined)
|
||||
}
|
||||
placeholder={
|
||||
systemLocale
|
||||
? `e.g., ${systemLocale.country}`
|
||||
: "e.g., US, GB, DE"
|
||||
}
|
||||
/>
|
||||
{!isAutoLocationEnabled && (
|
||||
<div className="space-y-3">
|
||||
<Label>Geolocation</Label>
|
||||
<div className="mb-4 p-3 bg-amber-50 rounded-md border border-amber-200">
|
||||
<p className="text-sm text-amber-800">
|
||||
⚠️ Warning: Configuring variables yourself may not always work due
|
||||
to underlying technology. It's recommended to use automatic
|
||||
location configuration.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Timezone</Label>
|
||||
<Select
|
||||
value={config.timezone || "auto"}
|
||||
onValueChange={(value) =>
|
||||
onConfigChange("timezone", value === "auto" ? undefined : value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={
|
||||
isLoadingSystemDefaults ? "Loading..." : "Select timezone"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="auto">
|
||||
{isLoadingSystemDefaults
|
||||
? "Auto (loading...)"
|
||||
: `Auto (${systemTimezone?.timezone || "UTC"})`}
|
||||
</SelectItem>
|
||||
{timezoneOptions.map((tz) => (
|
||||
<SelectItem key={tz.value} value={tz.value}>
|
||||
{tz.label}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="country">Country</Label>
|
||||
<Input
|
||||
id="country"
|
||||
value={config.country || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange("country", e.target.value || undefined)
|
||||
}
|
||||
placeholder={
|
||||
systemLocale
|
||||
? `e.g., ${systemLocale.country}`
|
||||
: "e.g., US, GB, DE"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Timezone</Label>
|
||||
<Select
|
||||
value={config.timezone || "auto"}
|
||||
onValueChange={(value) =>
|
||||
onConfigChange(
|
||||
"timezone",
|
||||
value === "auto" ? undefined : value,
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={
|
||||
isLoadingSystemDefaults ? "Loading..." : "Select timezone"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="auto">
|
||||
{isLoadingSystemDefaults
|
||||
? "Auto (loading...)"
|
||||
: `Auto (${systemTimezone?.timezone || "UTC"})`}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{timezoneOptions.map((tz) => (
|
||||
<SelectItem key={tz.value} value={tz.value}>
|
||||
{tz.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="latitude">Latitude</Label>
|
||||
<Input
|
||||
id="latitude"
|
||||
type="number"
|
||||
step="any"
|
||||
value={config.latitude || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange(
|
||||
"latitude",
|
||||
e.target.value ? parseFloat(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
placeholder="e.g., 40.7128"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="longitude">Longitude</Label>
|
||||
<Input
|
||||
id="longitude"
|
||||
type="number"
|
||||
step="any"
|
||||
value={config.longitude || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange(
|
||||
"longitude",
|
||||
e.target.value ? parseFloat(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
placeholder="e.g., -74.0060"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="latitude">Latitude</Label>
|
||||
<Input
|
||||
id="latitude"
|
||||
type="number"
|
||||
step="any"
|
||||
value={config.latitude || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange(
|
||||
"latitude",
|
||||
e.target.value ? parseFloat(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
placeholder="e.g., 40.7128"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="longitude">Longitude</Label>
|
||||
<Input
|
||||
id="longitude"
|
||||
type="number"
|
||||
step="any"
|
||||
value={config.longitude || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange(
|
||||
"longitude",
|
||||
e.target.value ? parseFloat(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
placeholder="e.g., -74.0060"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Localization */}
|
||||
<div className="space-y-3">
|
||||
<Label>Locale</Label>
|
||||
<Select
|
||||
value={config.locale?.[0] || ""}
|
||||
onValueChange={(value) =>
|
||||
onConfigChange("locale", value ? [value] : undefined)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={
|
||||
isLoadingSystemDefaults
|
||||
? "Loading..."
|
||||
: `Select locale (system: ${systemLocale?.locale || "unknown"})`
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{!isLoadingSystemDefaults && systemLocale && (
|
||||
<SelectItem value={systemLocale.locale}>
|
||||
{systemLocale.locale} (System Default)
|
||||
</SelectItem>
|
||||
)}
|
||||
{localeOptions.map((locale) => (
|
||||
<SelectItem key={locale.value} value={locale.value}>
|
||||
{locale.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{!isAutoLocationEnabled && (
|
||||
<div className="space-y-3">
|
||||
<Label>Locale</Label>
|
||||
<Select
|
||||
value={config.locale?.[0] || ""}
|
||||
onValueChange={(value) =>
|
||||
onConfigChange("locale", value ? [value] : undefined)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={
|
||||
isLoadingSystemDefaults
|
||||
? "Loading..."
|
||||
: `Select locale (system: ${systemLocale?.locale || "unknown"})`
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{!isLoadingSystemDefaults && systemLocale && (
|
||||
<SelectItem value={systemLocale.locale}>
|
||||
{systemLocale.locale} (System Default)
|
||||
</SelectItem>
|
||||
)}
|
||||
{localeOptions.map((locale) => (
|
||||
<SelectItem key={locale.value} value={locale.value}>
|
||||
{locale.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Screen Resolution */}
|
||||
<div className="space-y-3">
|
||||
@@ -469,72 +481,6 @@ export function SharedCamoufoxConfigForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Window Size */}
|
||||
<div className="space-y-3">
|
||||
<Label>Window Size</Label>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="window-width">Width</Label>
|
||||
<Input
|
||||
id="window-width"
|
||||
type="number"
|
||||
value={config.window_width || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange(
|
||||
"window_width",
|
||||
e.target.value ? parseInt(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
placeholder="e.g., 1366"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="window-height">Height</Label>
|
||||
<Input
|
||||
id="window-height"
|
||||
type="number"
|
||||
value={config.window_height || ""}
|
||||
onChange={(e) =>
|
||||
onConfigChange(
|
||||
"window_height",
|
||||
e.target.value ? parseInt(e.target.value) : undefined,
|
||||
)
|
||||
}
|
||||
placeholder="e.g., 768"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Advanced Options */}
|
||||
<div className="space-y-3">
|
||||
<Label>Advanced Options</Label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="enable-cache"
|
||||
checked={config.enable_cache || false}
|
||||
onCheckedChange={(checked) =>
|
||||
onConfigChange("enable_cache", checked)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="enable-cache">Enable Browser Cache</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="main-world-eval"
|
||||
checked={config.main_world_eval || false}
|
||||
onCheckedChange={(checked) =>
|
||||
onConfigChange("main_world_eval", checked)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="main-world-eval">
|
||||
Enable Main World Evaluation
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* WebGL Settings */}
|
||||
<div className="space-y-3">
|
||||
<Label>WebGL Settings</Label>
|
||||
|
||||
Reference in New Issue
Block a user