Compare commits

..

6 Commits

Author SHA1 Message Date
zhom e5f0621599 refactor: temporarily remove camoufox-js 2025-07-09 06:04:52 +04:00
zhom d6e940b29f style: copy 2025-07-09 06:02:28 +04:00
zhom 6dfa69608f refactor: switch nodecar to commonjs 2025-07-09 05:58:47 +04:00
zhom add4f6d3f8 refactor: popular camoufox data via env variable 2025-07-08 19:11:30 +04:00
zhom a1403c88f9 style: copy 2025-07-08 17:36:58 +04:00
zhom c9974a8071 style: copy 2025-07-08 06:41:09 +04:00
15 changed files with 402 additions and 1541 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
23
24
+1 -1
View File
@@ -1 +1 @@
23
24
+10
View File
@@ -8,6 +8,7 @@
"autoconfig",
"autologin",
"biomejs",
"CAMOU",
"camoufox",
"cdylib",
"CFURL",
@@ -16,6 +17,7 @@
"clippy",
"cmdk",
"codegen",
"codesign",
"CTYPE",
"datareporting",
"devedition",
@@ -50,7 +52,9 @@
"librsvg",
"libwebkit",
"libxdo",
"localipv",
"localtime",
"memorysaver",
"mmdb",
"mountpoint",
"msiexec",
@@ -66,9 +70,12 @@
"objc",
"orhun",
"osascript",
"oscpu",
"peerconnection",
"pixbuf",
"pkgman",
"plasmohq",
"postject",
"prefs",
"propertylist",
"reqwest",
@@ -78,6 +85,7 @@
"SARIF",
"serde",
"shadcn",
"showcursor",
"signon",
"sonner",
"splitn",
@@ -85,6 +93,7 @@
"staticlib",
"stefanzweifel",
"subdirs",
"Subframes",
"subkey",
"SUPPRESSMSGBOXES",
"swatinem",
@@ -107,6 +116,7 @@
"urlencoding",
"vercel",
"VERYSILENT",
"virtdisplay",
"webgl",
"webrtc",
"winreg",
+1
View File
@@ -38,6 +38,7 @@
## Features
- Create unlimited number of local browser profiles completely isolated from each other
- Bypass website restrictions and avoid getting banned by using anti-detection features powered by [Camoufox](https://camoufox.com/)
- Proxy support with basic auth for all browsers except for TOR Browser
- Import profiles from your existing browsers
- Automatic updates both for browsers and for the app itself
+16 -8
View File
@@ -3,19 +3,20 @@
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"type": "commonjs",
"scripts": {
"watch": "nodemon --exec ts-node --esm ./src/index.ts --watch src",
"dev": "node --loader ts-node/esm ./src/index.ts",
"start": "tsc && node ./dist/index.js",
"test": "tsc && node ./dist/test-proxy.js",
"rename-binary": "sh ./copy-binary.sh",
"build": "tsc && pkg ./dist/index.js --targets latest-macos-arm64 --output dist/nodecar && pnpm rename-binary",
"build:mac-aarch64": "tsc && pkg ./dist/index.js --targets latest-macos-arm64 --output dist/nodecar && pnpm rename-binary",
"build:mac-x86_64": "tsc && pkg ./dist/index.js --targets latest-macos-x64 --output dist/nodecar && pnpm rename-binary",
"build:linux-x64": "tsc && pkg ./dist/index.js --targets latest-linux-x64 --output dist/nodecar && pnpm rename-binary",
"build:linux-arm64": "tsc && pkg ./dist/index.js --targets latest-linux-arm64 --output dist/nodecar && pnpm rename-binary",
"build:win-x64": "tsc && pkg ./dist/index.js --targets latest-win-x64 --output dist/nodecar && pnpm rename-binary",
"build:win-arm64": "tsc && pkg ./dist/index.js --targets latest-win-arm64 --output dist/nodecar && pnpm rename-binary"
"build": "tsc && pkg ./dist/index.js --targets latest-macos-arm64 --output dist/nodecar --option experimental-require-module --public && pnpm rename-binary",
"build:mac-aarch64": "tsc && pkg ./dist/index.js --targets latest-macos-arm64 --output dist/nodecar --option experimental-require-module --public&& pnpm rename-binary",
"build:mac-x86_64": "tsc && pkg ./dist/index.js --targets latest-macos-x64 --output dist/nodecar --option experimental-require-module --public && pnpm rename-binary",
"build:linux-x64": "tsc && pkg ./dist/index.js --targets latest-linux-x64 --output dist/nodecar --option experimental-require-module --public && pnpm rename-binary",
"build:linux-arm64": "tsc && pkg ./dist/index.js --targets latest-linux-arm64 --output dist/nodecar --option experimental-require-module --public && pnpm rename-binary",
"build:win-x64": "tsc && pkg ./dist/index.js --targets latest-win-x64 --output dist/nodecar --option experimental-require-module --public && pnpm rename-binary",
"build:win-arm64": "tsc && pkg ./dist/index.js --targets latest-win-arm64 --output dist/nodecar --option experimental-require-module --public && pnpm rename-binary"
},
"keywords": [],
"author": "",
@@ -23,7 +24,6 @@
"dependencies": {
"@types/node": "^24.0.10",
"@yao-pkg/pkg": "^6.5.1",
"camoufox-js": "^0.6.0",
"commander": "^14.0.0",
"dotenv": "^17.0.1",
"get-port": "^7.1.0",
@@ -35,5 +35,13 @@
},
"devDependencies": {
"@types/tmp": "^0.2.6"
},
"pkg": {
"assets": [
"node_modules/camoufox-js/**/*"
],
"scripts": [
"node_modules/camoufox-js/**/*.js"
]
}
}
+138 -238
View File
@@ -14,7 +14,7 @@ export interface CamoufoxConfig {
export interface CamoufoxLaunchOptions {
// Operating system to use for fingerprint generation
os?: "windows" | "macos" | "linux" | string[];
os?: "windows" | "macos" | "linux" | ("windows" | "macos" | "linux")[];
// Blocking options
block_images?: boolean;
@@ -37,7 +37,7 @@ export interface CamoufoxLaunchOptions {
addons?: string[];
fonts?: string[];
custom_fonts_only?: boolean;
exclude_addons?: string[];
exclude_addons?: "UBO"[];
// Screen and window
screen?: {
@@ -82,7 +82,7 @@ export interface CamoufoxLaunchOptions {
virtual_display?: string;
webgl_config?: [string, string];
// Custom options
// Custom options - these may not be directly supported by camoufox-js
timezone?: string;
country?: string;
geolocation?: {
@@ -90,6 +90,12 @@ export interface CamoufoxLaunchOptions {
longitude: number;
accuracy?: number;
};
// Add i_know_what_im_doing to match camoufox-js
i_know_what_im_doing?: boolean;
// Allow any additional properties that camoufox-js might accept
[key: string]: any;
}
// Store for active Camoufox processes
@@ -186,184 +192,6 @@ function isProcessRunning(pid: number): boolean {
}
}
/**
* Convert Camoufox options to command line arguments
*/
function buildCamoufoxArgs(
options: CamoufoxLaunchOptions,
profilePath: string,
url?: string,
): string[] {
const args: string[] = [];
// Always use profile
args.push("-profile", profilePath);
// Cache enabled by default as requested
if (options.enable_cache !== false) {
// Cache is enabled by default in Camoufox, no special args needed
}
// Headless mode
if (options.headless) {
args.push("-headless");
}
// No remote for security (anti-detect)
args.push("-no-remote");
// Custom Firefox user preferences will be written to user.js in profile
// Additional custom args
if (options.args) {
args.push(...options.args);
}
// URL to open
if (url) {
args.push(url);
}
return args;
}
/**
* Create user.js file with Camoufox preferences
*/
function createUserJs(
profilePath: string,
options: CamoufoxLaunchOptions,
): void {
const preferences: string[] = [];
// Anti-detect preferences
preferences.push('user_pref("privacy.resistFingerprinting", true);');
preferences.push(
'user_pref("privacy.resistFingerprinting.letterboxing", true);',
);
preferences.push('user_pref("privacy.trackingprotection.enabled", true);');
// Disable telemetry and data collection
preferences.push(
'user_pref("datareporting.healthreport.uploadEnabled", false);',
);
preferences.push(
'user_pref("datareporting.policy.dataSubmissionEnabled", false);',
);
preferences.push('user_pref("toolkit.telemetry.enabled", false);');
preferences.push('user_pref("toolkit.telemetry.unified", false);');
// Block options
if (options.block_images) {
preferences.push('user_pref("permissions.default.image", 2);');
}
if (options.block_webrtc) {
preferences.push('user_pref("media.peerconnection.enabled", false);');
preferences.push('user_pref("media.navigator.enabled", false);');
}
if (options.block_webgl) {
preferences.push('user_pref("webgl.disabled", true);');
preferences.push('user_pref("webgl.disable-extensions", true);');
}
// COOP settings
if (options.disable_coop) {
preferences.push(
'user_pref("browser.tabs.remote.useCrossOriginOpenerPolicy", false);',
);
}
// Locale settings
if (options.locale) {
const localeStr = Array.isArray(options.locale)
? options.locale[0]
: options.locale;
preferences.push(`user_pref("intl.locale.requested", "${localeStr}");`);
preferences.push(`user_pref("general.useragent.locale", "${localeStr}");`);
}
// Timezone
if (options.timezone) {
preferences.push(
`user_pref("privacy.resistFingerprinting.timezone", "${options.timezone}");`,
);
}
// Custom Firefox preferences
if (options.firefox_user_prefs) {
for (const [key, value] of Object.entries(options.firefox_user_prefs)) {
if (typeof value === "string") {
preferences.push(`user_pref("${key}", "${value}");`);
} else if (typeof value === "boolean") {
preferences.push(`user_pref("${key}", ${value});`);
} else if (typeof value === "number") {
preferences.push(`user_pref("${key}", ${value});`);
}
}
}
// Proxy settings
if (options.proxy) {
if (typeof options.proxy === "string") {
// Parse proxy URL
try {
const proxyUrl = new URL(options.proxy);
const port =
parseInt(proxyUrl.port) ||
(proxyUrl.protocol === "https:" ? 443 : 80);
if (proxyUrl.protocol.startsWith("socks")) {
preferences.push('user_pref("network.proxy.type", 1);');
preferences.push(
`user_pref("network.proxy.socks", "${proxyUrl.hostname}");`,
);
preferences.push(`user_pref("network.proxy.socks_port", ${port});`);
if (proxyUrl.protocol === "socks5:") {
preferences.push('user_pref("network.proxy.socks_version", 5);');
} else {
preferences.push('user_pref("network.proxy.socks_version", 4);');
}
} else {
preferences.push('user_pref("network.proxy.type", 1);');
preferences.push(
`user_pref("network.proxy.http", "${proxyUrl.hostname}");`,
);
preferences.push(`user_pref("network.proxy.http_port", ${port});`);
preferences.push(
`user_pref("network.proxy.ssl", "${proxyUrl.hostname}");`,
);
preferences.push(`user_pref("network.proxy.ssl_port", ${port});`);
}
if (proxyUrl.username && proxyUrl.password) {
// Note: Basic auth for proxies is handled differently in modern Firefox
preferences.push(
'user_pref("network.proxy.allow_hijacking_localhost", true);',
);
}
} catch (error) {
console.error(`Invalid proxy URL: ${options.proxy}`);
}
}
}
// Geolocation
if (options.geolocation) {
preferences.push('user_pref("geo.enabled", true);');
preferences.push(
`user_pref("geo.wifi.uri", "data:application/json,{\\"location\\": {\\"lat\\": ${options.geolocation.latitude}, \\"lng\\": ${options.geolocation.longitude}}, \\"accuracy\\": ${options.geolocation.accuracy || 100}}");`,
);
} else {
preferences.push('user_pref("geo.enabled", false);');
}
// Write user.js file
const userJsPath = path.join(profilePath, "user.js");
fs.writeFileSync(userJsPath, preferences.join("\n"));
}
/**
* Launch Camoufox browser with specified options
*/
@@ -380,63 +208,130 @@ export async function launchCamoufox(
fs.mkdirSync(profilePath, { recursive: true });
}
// Create user.js with preferences
createUserJs(profilePath, options);
try {
// Use camoufox-js launchOptions to generate proper configuration
// const { launchOptions } = require("camoufox-js");
// const launchConfig = await launchOptions({
// ...options,
// executable_path: executablePath,
// // Enable debug if requested
// debug: options.debug || false,
// // Set i_know_what_im_doing to true to bypass warnings since we're controlling this
// i_know_what_im_doing: true,
// });
//
const launchConfig: any = {};
// Build command line arguments
const args = buildCamoufoxArgs(options, profilePath, url);
if (options.debug) {
console.log(
"Generated launch config:",
JSON.stringify(launchConfig, null, 2),
);
}
// Prepare environment variables
const env = {
...process.env,
...options.env,
};
// Extract the command line args and environment from the launch config
const args = [
"-profile",
profilePath,
"-no-remote",
...(launchConfig.args || []),
];
// Handle virtual display
if (options.virtual_display) {
env.DISPLAY = options.virtual_display;
// Add URL if provided
if (url) {
args.push(url);
}
// Use the environment variables and other config from camoufox-js
const env: NodeJS.ProcessEnv = {
...process.env,
...(launchConfig.env || {}),
};
if (options.debug) {
console.log("Launch args:", args);
console.log(
"Environment variables set:",
Object.keys(env).filter(
(key) => key.startsWith("CAMOU_") || key.startsWith("DISPLAY"),
),
);
}
// Use the executable path from the launch config if available
const finalExecutablePath = launchConfig.executablePath || executablePath;
// Write Firefox user preferences to user.js if provided
if (
launchConfig.firefoxUserPrefs &&
Object.keys(launchConfig.firefoxUserPrefs).length > 0
) {
const userJsPath = path.join(profilePath, "user.js");
const preferences: string[] = [];
for (const [key, value] of Object.entries(
launchConfig.firefoxUserPrefs,
)) {
if (typeof value === "string") {
preferences.push(`user_pref("${key}", "${value}");`);
} else if (typeof value === "boolean") {
preferences.push(`user_pref("${key}", ${value});`);
} else if (typeof value === "number") {
preferences.push(`user_pref("${key}", ${value});`);
}
}
if (preferences.length > 0) {
fs.writeFileSync(userJsPath, preferences.join("\n"));
}
}
// Launch the process
const child = spawn(finalExecutablePath, args, {
env: env as NodeJS.ProcessEnv,
detached: true,
stdio: options.debug ? "inherit" : "ignore",
});
if (!child.pid) {
throw new Error("Failed to launch Camoufox process");
}
const config: CamoufoxConfig = {
id,
pid: child.pid,
executablePath: finalExecutablePath,
profilePath,
url,
options,
};
// Save configuration
saveCamoufoxConfig(config);
// Handle process exit
child.on("exit", (code, signal) => {
if (options.debug) {
console.log(
`Camoufox process ${child.pid} exited with code ${code}, signal ${signal}`,
);
}
deleteCamoufoxConfig(id);
});
child.on("error", (error) => {
console.error(`Camoufox process error: ${error}`);
deleteCamoufoxConfig(id);
});
// Detach the child process so it can continue running independently
child.unref();
return config;
} catch (error) {
console.error(`Failed to launch Camoufox: ${error}`);
throw error;
}
// Launch the process
const child = spawn(executablePath, args, {
env: env as NodeJS.ProcessEnv,
detached: true,
stdio: options.debug ? "inherit" : "ignore",
});
if (!child.pid) {
throw new Error("Failed to launch Camoufox process");
}
const config: CamoufoxConfig = {
id,
pid: child.pid,
executablePath,
profilePath,
url,
options,
};
// Save configuration
saveCamoufoxConfig(config);
// Handle process exit
child.on("exit", (code, signal) => {
console.log(
`Camoufox process ${child.pid} exited with code ${code}, signal ${signal}`,
);
deleteCamoufoxConfig(id);
});
child.on("error", (error) => {
console.error(`Camoufox process error: ${error}`);
deleteCamoufoxConfig(id);
});
// Detach the child process so it can continue running independently
child.unref();
return config;
}
/**
@@ -481,14 +376,19 @@ export function listCamoufoxProcesses(): any[] {
for (const [id, config] of activeCamoufoxProcesses) {
if (config.pid && isProcessRunning(config.pid)) {
// Ensure we have the required fields, fall back to empty strings if missing
const executablePath = config.executablePath || "";
const profilePath = config.profilePath || "";
// Return in snake_case format for Rust compatibility
// Always include executable_path and profile_path, even if empty
activeConfigs.push({
id: config.id,
pid: config.pid,
executable_path: config.executablePath,
profile_path: config.profilePath,
url: config.url,
options: config.options,
executable_path: executablePath,
profile_path: profilePath,
url: config.url || null,
options: config.options || {},
});
} else {
// Clean up dead processes
+62 -31
View File
@@ -71,7 +71,7 @@ program
"Error: Either --upstream URL or --host, --proxy-port, and --type are required",
);
console.log(
"Example: proxy start --host datacenter.proxyempire.io --proxy-port 9000 --type http --username user --password pass",
"Example: proxy start --host example.com --proxy-port 9000 --type http --username user --password pass",
);
process.exit(1);
return;
@@ -221,6 +221,12 @@ program
.option("--webgl-vendor <vendor>", "WebGL vendor string")
.option("--webgl-renderer <renderer>", "WebGL renderer string")
// Fingerprint
.option(
"--fingerprint <fingerprint>",
"custom BrowserForge fingerprint (JSON string)",
)
// Proxy
.option(
"--proxy <proxy>",
@@ -264,6 +270,18 @@ program
: options.os;
}
// Set geolocation from individual latitude/longitude values
if (options.latitude && options.longitude) {
camoufoxOptions.geolocation = {
latitude: options.latitude,
longitude: options.longitude,
};
}
// Set timezone and country only if explicitly provided
if (options.country) camoufoxOptions.country = options.country;
if (options.timezone) camoufoxOptions.timezone = options.timezone;
// Blocking options
if (options.blockImages) camoufoxOptions.block_images = true;
if (options.blockWebrtc) camoufoxOptions.block_webrtc = true;
@@ -272,20 +290,11 @@ program
// Security options
if (options.disableCoop) camoufoxOptions.disable_coop = true;
// Geolocation
// Geolocation IP
if (options.geoip) {
camoufoxOptions.geoip =
options.geoip === "auto" ? true : options.geoip;
}
if (options.latitude && options.longitude) {
camoufoxOptions.geolocation = {
latitude: options.latitude,
longitude: options.longitude,
accuracy: 100,
};
}
if (options.country) camoufoxOptions.country = options.country;
if (options.timezone) camoufoxOptions.timezone = options.timezone;
// UI and behavior
if (options.humanize) camoufoxOptions.humanize = options.humanize;
@@ -295,24 +304,40 @@ program
if (options.locale) {
camoufoxOptions.locale = options.locale.includes(",")
? options.locale.split(",")
: options.locale;
: [options.locale];
}
// Extensions and fonts
if (options.addons) camoufoxOptions.addons = options.addons.split(",");
if (options.fonts) camoufoxOptions.fonts = options.fonts.split(",");
if (options.customFontsOnly) camoufoxOptions.custom_fonts_only = true;
if (options.excludeAddons)
camoufoxOptions.exclude_addons = options.excludeAddons.split(",");
if (options.excludeAddons) {
// Only support UBO for now as that's what camoufox-js supports
const excludeList = options.excludeAddons.split(",");
if (excludeList.includes("UBO") || excludeList.includes("ubo")) {
camoufoxOptions.exclude_addons = ["UBO"];
}
}
// Screen and window
const screen: any = {};
if (options.screenMinWidth) screen.minWidth = options.screenMinWidth;
if (options.screenMaxWidth) screen.maxWidth = options.screenMaxWidth;
if (options.screenMinHeight) screen.minHeight = options.screenMinHeight;
if (options.screenMaxHeight) screen.maxHeight = options.screenMaxHeight;
if (Object.keys(screen).length > 0) camoufoxOptions.screen = screen;
// Screen dimensions - combine into screen object
if (
options.screenMinWidth ||
options.screenMaxWidth ||
options.screenMinHeight ||
options.screenMaxHeight
) {
camoufoxOptions.screen = {};
if (options.screenMinWidth)
camoufoxOptions.screen.minWidth = options.screenMinWidth;
if (options.screenMaxWidth)
camoufoxOptions.screen.maxWidth = options.screenMaxWidth;
if (options.screenMinHeight)
camoufoxOptions.screen.minHeight = options.screenMinHeight;
if (options.screenMaxHeight)
camoufoxOptions.screen.maxHeight = options.screenMaxHeight;
}
// Window dimensions - combine into window tuple
if (options.windowWidth && options.windowHeight) {
camoufoxOptions.window = [options.windowWidth, options.windowHeight];
}
@@ -320,6 +345,8 @@ program
// Advanced options
if (options.ffVersion) camoufoxOptions.ff_version = options.ffVersion;
if (options.mainWorldEval) camoufoxOptions.main_world_eval = true;
// WebGL - combine vendor and renderer into webgl_config tuple
if (options.webglVendor && options.webglRenderer) {
camoufoxOptions.webgl_config = [
options.webglVendor,
@@ -327,6 +354,17 @@ program
];
}
// Fingerprint
if (options.fingerprint) {
try {
camoufoxOptions.fingerprint = JSON.parse(options.fingerprint);
} catch (e) {
console.error("Invalid JSON for --fingerprint option");
process.exit(1);
return;
}
}
// Proxy
if (options.proxy) camoufoxOptions.proxy = options.proxy;
@@ -389,16 +427,9 @@ program
console.log(JSON.stringify({ success }));
process.exit(0);
} else if (action === "list") {
const processes = listCamoufoxProcesses();
// Convert camelCase to snake_case for Rust compatibility
const rustCompatibleProcesses = processes.map((process) => ({
id: process.id,
pid: process.pid,
executable_path: process.executablePath,
profile_path: process.profilePath,
url: process.url,
}));
console.log(JSON.stringify(rustCompatibleProcesses));
const processes = await listCamoufoxProcesses();
// The processes already have snake_case properties, no conversion needed
console.log(JSON.stringify(processes));
process.exit(0);
} else if (action === "open-url") {
if (!options.id || !options.url) {
+2 -2
View File
@@ -2,7 +2,7 @@
"name": "donutbrowser",
"private": true,
"license": "AGPL-3.0",
"version": "0.7.2",
"version": "0.7.1",
"type": "module",
"scripts": {
"dev": "next dev --turbopack",
@@ -58,7 +58,7 @@
"@biomejs/biome": "2.0.6",
"@tailwindcss/postcss": "^4.1.11",
"@tauri-apps/cli": "^2.6.2",
"@types/node": "^24.0.13",
"@types/node": "^24.0.11",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
+120 -1203
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -971,7 +971,7 @@ dependencies = [
[[package]]
name = "donutbrowser"
version = "0.7.2"
version = "0.7.1"
dependencies = [
"async-trait",
"base64 0.22.1",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "donutbrowser"
version = "0.7.2"
version = "0.7.1"
description = "Simple Yet Powerful Browser Orchestrator"
authors = ["zhom@github"]
edition = "2021"
+14 -3
View File
@@ -30,6 +30,7 @@ pub struct CamoufoxConfig {
pub screen_max_height: Option<u32>,
pub window_width: Option<u32>,
pub window_height: Option<u32>,
pub fingerprint: Option<serde_json::Value>,
pub ff_version: Option<u32>,
pub main_world_eval: Option<bool>,
pub webgl_vendor: Option<String>,
@@ -70,6 +71,7 @@ impl Default for CamoufoxConfig {
screen_max_height: None,
window_width: None,
window_height: None,
fingerprint: None,
ff_version: None,
main_world_eval: None,
webgl_vendor: None,
@@ -265,6 +267,13 @@ impl CamoufoxLauncher {
}
}
// Fingerprint
if let Some(fingerprint) = &config.fingerprint {
let fingerprint_json = serde_json::to_string(fingerprint)
.map_err(|e| format!("Failed to serialize fingerprint: {e}"))?;
sidecar = sidecar.arg("--fingerprint").arg(fingerprint_json);
}
if let Some(proxy) = &config.proxy {
sidecar = sidecar.arg("--proxy").arg(proxy);
}
@@ -457,9 +466,7 @@ impl CamoufoxLauncher {
.and_then(|v| v.as_str())
.or_else(|| process.get("profilePath").and_then(|v| v.as_str()));
if let (Some(id), Some(executable_path), Some(profile_path)) =
(id, executable_path, profile_path)
{
if let Some(id) = id {
let pid = process
.get("pid")
.and_then(|v| v.as_u64())
@@ -470,6 +477,10 @@ impl CamoufoxLauncher {
.and_then(|v| v.as_str())
.map(|s| s.to_string());
// Use empty strings if executable_path or profile_path are missing
let executable_path = executable_path.unwrap_or("");
let profile_path = profile_path.unwrap_or("");
results.push(CamoufoxLaunchResult {
id: id.to_string(),
pid,
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Donut Browser",
"version": "0.7.2",
"version": "0.7.1",
"identifier": "com.donutbrowser",
"build": {
"beforeDevCommand": "pnpm dev",
+3 -9
View File
@@ -141,9 +141,7 @@ export default function Home() {
const profileList = await invoke<BrowserProfile[]>(
"list_browser_profiles",
);
setProfiles(
profileList.filter((profile) => profile.browser !== "camoufox"),
);
setProfiles(profileList);
// Check for missing binaries after loading profiles
await checkMissingBinaries();
@@ -191,9 +189,7 @@ export default function Home() {
const profileList = await invoke<BrowserProfile[]>(
"list_browser_profiles",
);
setProfiles(
profileList.filter((profile) => profile.browser !== "camoufox"),
);
setProfiles(profileList);
// TODO: remove after a few version bumps, needed to properly display migrated profiles
setTimeout(async () => {
@@ -201,9 +197,7 @@ export default function Home() {
const profiles = await invoke<BrowserProfile[]>(
"list_browser_profiles",
);
setProfiles(
profiles.filter((profile) => profile.browser !== "camoufox"),
);
setProfiles(profiles);
}
await sleep(500);
}, 0);
+31 -42
View File
@@ -95,8 +95,6 @@ const browserOptions: BrowserOption[] = [
},
];
const IS_ANTI_DETECT_SUPPORTED = false;
export function CreateProfileDialog({
isOpen,
onClose,
@@ -388,54 +386,45 @@ 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">
Anti-Detect support is coming soon!
Powered by Camoufox
</p>
</div>
</TabsContent>
{IS_ANTI_DETECT_SUPPORTED && (
<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") &&
camoufoxReleaseTypes.stable && (
<div className="flex gap-3 items-center p-3 bg-amber-50 rounded-md border border-amber-200">
<p className="text-sm text-amber-800">
Camoufox version ({camoufoxReleaseTypes.stable})
needs to be downloaded
</p>
<LoadingButton
onClick={() => handleDownload("camoufox")}
isLoading={isBrowserDownloading("camoufox")}
size="sm"
disabled={isBrowserDownloading("camoufox")}
>
Download
</LoadingButton>
</div>
)}
{isBrowserVersionAvailable("camoufox") && (
<div className="p-3 text-sm text-green-600 bg-green-50 rounded-md border border-green-200">
Camoufox version ({camoufoxReleaseTypes.stable}) is
available
<div className="space-y-6">
{/* Camoufox Download Status */}
{!isBrowserVersionAvailable("camoufox") &&
camoufoxReleaseTypes.stable && (
<div className="flex gap-3 items-center p-3 bg-amber-50 rounded-md border border-amber-200">
<p className="text-sm text-amber-800">
Camoufox version ({camoufoxReleaseTypes.stable}) needs
to be downloaded
</p>
<LoadingButton
onClick={() => handleDownload("camoufox")}
isLoading={isBrowserDownloading("camoufox")}
size="sm"
disabled={isBrowserDownloading("camoufox")}
>
Download
</LoadingButton>
</div>
)}
{isBrowserVersionAvailable("camoufox") && (
<div className="p-3 text-sm text-green-600 bg-green-50 rounded-md border border-green-200">
Camoufox version ({camoufoxReleaseTypes.stable}) is
available
</div>
)}
<SharedCamoufoxConfigForm
config={camoufoxConfig}
onConfigChange={updateCamoufoxConfig}
/>
</div>
</TabsContent>
)}
<SharedCamoufoxConfigForm
config={camoufoxConfig}
onConfigChange={updateCamoufoxConfig}
/>
</div>
</TabsContent>
{/* Proxy Selection - Common to both tabs - Compact without card */}
{storedProxies.length > 0 && (