mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-07-25 13:50:51 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb7e88e867 | |||
| e5f0621599 | |||
| d6e940b29f | |||
| 6dfa69608f | |||
| add4f6d3f8 | |||
| a1403c88f9 | |||
| c9974a8071 |
@@ -52,7 +52,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
prompt-file: issue_analysis.txt
|
prompt-file: issue_analysis.txt
|
||||||
system-prompt: |
|
system-prompt: |
|
||||||
You are an issue validation assistant for Donut Browser, an anti-detect browser.
|
You are an issue validation assistant for Donut Browser, an browser orchestrator.
|
||||||
|
|
||||||
Analyze the provided issue content and determine if it contains sufficient information based on these requirements:
|
Analyze the provided issue content and determine if it contains sufficient information based on these requirements:
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
prompt-file: commits.txt
|
prompt-file: commits.txt
|
||||||
system-prompt: |
|
system-prompt: |
|
||||||
You are an expert technical writer tasked with generating comprehensive release notes for Donut Browser, a powerful anti-detect browser.
|
You are an expert technical writer tasked with generating comprehensive release notes for Donut Browser, a powerful browser orchestrator.
|
||||||
|
|
||||||
Analyze the provided commit messages and generate well-structured release notes following this format:
|
Analyze the provided commit messages and generate well-structured release notes following this format:
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
23
|
24
|
||||||
|
|
||||||
|
|||||||
Vendored
+10
@@ -8,6 +8,7 @@
|
|||||||
"autoconfig",
|
"autoconfig",
|
||||||
"autologin",
|
"autologin",
|
||||||
"biomejs",
|
"biomejs",
|
||||||
|
"CAMOU",
|
||||||
"camoufox",
|
"camoufox",
|
||||||
"cdylib",
|
"cdylib",
|
||||||
"CFURL",
|
"CFURL",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"clippy",
|
"clippy",
|
||||||
"cmdk",
|
"cmdk",
|
||||||
"codegen",
|
"codegen",
|
||||||
|
"codesign",
|
||||||
"CTYPE",
|
"CTYPE",
|
||||||
"datareporting",
|
"datareporting",
|
||||||
"devedition",
|
"devedition",
|
||||||
@@ -50,7 +52,9 @@
|
|||||||
"librsvg",
|
"librsvg",
|
||||||
"libwebkit",
|
"libwebkit",
|
||||||
"libxdo",
|
"libxdo",
|
||||||
|
"localipv",
|
||||||
"localtime",
|
"localtime",
|
||||||
|
"memorysaver",
|
||||||
"mmdb",
|
"mmdb",
|
||||||
"mountpoint",
|
"mountpoint",
|
||||||
"msiexec",
|
"msiexec",
|
||||||
@@ -66,9 +70,12 @@
|
|||||||
"objc",
|
"objc",
|
||||||
"orhun",
|
"orhun",
|
||||||
"osascript",
|
"osascript",
|
||||||
|
"oscpu",
|
||||||
"peerconnection",
|
"peerconnection",
|
||||||
"pixbuf",
|
"pixbuf",
|
||||||
|
"pkgman",
|
||||||
"plasmohq",
|
"plasmohq",
|
||||||
|
"postject",
|
||||||
"prefs",
|
"prefs",
|
||||||
"propertylist",
|
"propertylist",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -78,6 +85,7 @@
|
|||||||
"SARIF",
|
"SARIF",
|
||||||
"serde",
|
"serde",
|
||||||
"shadcn",
|
"shadcn",
|
||||||
|
"showcursor",
|
||||||
"signon",
|
"signon",
|
||||||
"sonner",
|
"sonner",
|
||||||
"splitn",
|
"splitn",
|
||||||
@@ -85,6 +93,7 @@
|
|||||||
"staticlib",
|
"staticlib",
|
||||||
"stefanzweifel",
|
"stefanzweifel",
|
||||||
"subdirs",
|
"subdirs",
|
||||||
|
"Subframes",
|
||||||
"subkey",
|
"subkey",
|
||||||
"SUPPRESSMSGBOXES",
|
"SUPPRESSMSGBOXES",
|
||||||
"swatinem",
|
"swatinem",
|
||||||
@@ -107,6 +116,7 @@
|
|||||||
"urlencoding",
|
"urlencoding",
|
||||||
"vercel",
|
"vercel",
|
||||||
"VERYSILENT",
|
"VERYSILENT",
|
||||||
|
"virtdisplay",
|
||||||
"webgl",
|
"webgl",
|
||||||
"webrtc",
|
"webrtc",
|
||||||
"winreg",
|
"winreg",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="assets/logo.png" alt="Donut Browser Logo" width="150">
|
<img src="assets/logo.png" alt="Donut Browser Logo" width="150">
|
||||||
<h1>Donut Browser</h1>
|
<h1>Donut Browser</h1>
|
||||||
<strong>A powerful anti-detect browser that puts you in control of your browsing experience. 🍩</strong>
|
<strong>A powerful browser orchestrator that puts you in control of your browsing experience. 🍩</strong>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
## Donut Browser
|
## Donut Browser
|
||||||
|
|
||||||
> A free and open source anti-detect browser built with [Tauri](https://v2.tauri.app/).
|
> A free and open source browser orchestrator built with [Tauri](https://v2.tauri.app/).
|
||||||
|
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="assets/preview-dark.png" />
|
<source media="(prefers-color-scheme: dark)" srcset="assets/preview-dark.png" />
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Create unlimited number of local browser profiles completely isolated from each other
|
- 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
|
- Proxy support with basic auth for all browsers except for TOR Browser
|
||||||
- Import profiles from your existing browsers
|
- Import profiles from your existing browsers
|
||||||
- Automatic updates both for browsers and for the app itself
|
- Automatic updates both for browsers and for the app itself
|
||||||
|
|||||||
+16
-8
@@ -3,19 +3,20 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "nodemon --exec ts-node --esm ./src/index.ts --watch src",
|
"watch": "nodemon --exec ts-node --esm ./src/index.ts --watch src",
|
||||||
"dev": "node --loader ts-node/esm ./src/index.ts",
|
"dev": "node --loader ts-node/esm ./src/index.ts",
|
||||||
"start": "tsc && node ./dist/index.js",
|
"start": "tsc && node ./dist/index.js",
|
||||||
"test": "tsc && node ./dist/test-proxy.js",
|
"test": "tsc && node ./dist/test-proxy.js",
|
||||||
"rename-binary": "sh ./copy-binary.sh",
|
"rename-binary": "sh ./copy-binary.sh",
|
||||||
"build": "tsc && pkg ./dist/index.js --targets latest-macos-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 && 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 && 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 && 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 && 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 && 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 && 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": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -23,7 +24,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^24.0.10",
|
"@types/node": "^24.0.10",
|
||||||
"@yao-pkg/pkg": "^6.5.1",
|
"@yao-pkg/pkg": "^6.5.1",
|
||||||
"camoufox-js": "^0.6.0",
|
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
"dotenv": "^17.0.1",
|
"dotenv": "^17.0.1",
|
||||||
"get-port": "^7.1.0",
|
"get-port": "^7.1.0",
|
||||||
@@ -35,5 +35,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/tmp": "^0.2.6"
|
"@types/tmp": "^0.2.6"
|
||||||
|
},
|
||||||
|
"pkg": {
|
||||||
|
"assets": [
|
||||||
|
"node_modules/camoufox-js/**/*"
|
||||||
|
],
|
||||||
|
"scripts": [
|
||||||
|
"node_modules/camoufox-js/**/*.js"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+138
-238
@@ -14,7 +14,7 @@ export interface CamoufoxConfig {
|
|||||||
|
|
||||||
export interface CamoufoxLaunchOptions {
|
export interface CamoufoxLaunchOptions {
|
||||||
// Operating system to use for fingerprint generation
|
// Operating system to use for fingerprint generation
|
||||||
os?: "windows" | "macos" | "linux" | string[];
|
os?: "windows" | "macos" | "linux" | ("windows" | "macos" | "linux")[];
|
||||||
|
|
||||||
// Blocking options
|
// Blocking options
|
||||||
block_images?: boolean;
|
block_images?: boolean;
|
||||||
@@ -37,7 +37,7 @@ export interface CamoufoxLaunchOptions {
|
|||||||
addons?: string[];
|
addons?: string[];
|
||||||
fonts?: string[];
|
fonts?: string[];
|
||||||
custom_fonts_only?: boolean;
|
custom_fonts_only?: boolean;
|
||||||
exclude_addons?: string[];
|
exclude_addons?: "UBO"[];
|
||||||
|
|
||||||
// Screen and window
|
// Screen and window
|
||||||
screen?: {
|
screen?: {
|
||||||
@@ -82,7 +82,7 @@ export interface CamoufoxLaunchOptions {
|
|||||||
virtual_display?: string;
|
virtual_display?: string;
|
||||||
webgl_config?: [string, string];
|
webgl_config?: [string, string];
|
||||||
|
|
||||||
// Custom options
|
// Custom options - these may not be directly supported by camoufox-js
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
country?: string;
|
country?: string;
|
||||||
geolocation?: {
|
geolocation?: {
|
||||||
@@ -90,6 +90,12 @@ export interface CamoufoxLaunchOptions {
|
|||||||
longitude: number;
|
longitude: number;
|
||||||
accuracy?: 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
|
// 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
|
* Launch Camoufox browser with specified options
|
||||||
*/
|
*/
|
||||||
@@ -380,63 +208,130 @@ export async function launchCamoufox(
|
|||||||
fs.mkdirSync(profilePath, { recursive: true });
|
fs.mkdirSync(profilePath, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create user.js with preferences
|
try {
|
||||||
createUserJs(profilePath, options);
|
// 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
|
if (options.debug) {
|
||||||
const args = buildCamoufoxArgs(options, profilePath, url);
|
console.log(
|
||||||
|
"Generated launch config:",
|
||||||
|
JSON.stringify(launchConfig, null, 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare environment variables
|
// Extract the command line args and environment from the launch config
|
||||||
const env = {
|
const args = [
|
||||||
...process.env,
|
"-profile",
|
||||||
...options.env,
|
profilePath,
|
||||||
};
|
"-no-remote",
|
||||||
|
...(launchConfig.args || []),
|
||||||
|
];
|
||||||
|
|
||||||
// Handle virtual display
|
// Add URL if provided
|
||||||
if (options.virtual_display) {
|
if (url) {
|
||||||
env.DISPLAY = options.virtual_display;
|
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) {
|
for (const [id, config] of activeCamoufoxProcesses) {
|
||||||
if (config.pid && isProcessRunning(config.pid)) {
|
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
|
// Return in snake_case format for Rust compatibility
|
||||||
|
// Always include executable_path and profile_path, even if empty
|
||||||
activeConfigs.push({
|
activeConfigs.push({
|
||||||
id: config.id,
|
id: config.id,
|
||||||
pid: config.pid,
|
pid: config.pid,
|
||||||
executable_path: config.executablePath,
|
executable_path: executablePath,
|
||||||
profile_path: config.profilePath,
|
profile_path: profilePath,
|
||||||
url: config.url,
|
url: config.url || null,
|
||||||
options: config.options,
|
options: config.options || {},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Clean up dead processes
|
// Clean up dead processes
|
||||||
|
|||||||
+64
-33
@@ -71,7 +71,7 @@ program
|
|||||||
"Error: Either --upstream URL or --host, --proxy-port, and --type are required",
|
"Error: Either --upstream URL or --host, --proxy-port, and --type are required",
|
||||||
);
|
);
|
||||||
console.log(
|
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);
|
process.exit(1);
|
||||||
return;
|
return;
|
||||||
@@ -154,7 +154,7 @@ program
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Command for Camoufox anti-detect browser
|
// Command for Camoufox browser orchestrator
|
||||||
program
|
program
|
||||||
.command("camoufox")
|
.command("camoufox")
|
||||||
.argument("<action>", "launch, stop, list, or open-url for Camoufox browser")
|
.argument("<action>", "launch, stop, list, or open-url for Camoufox browser")
|
||||||
@@ -221,6 +221,12 @@ program
|
|||||||
.option("--webgl-vendor <vendor>", "WebGL vendor string")
|
.option("--webgl-vendor <vendor>", "WebGL vendor string")
|
||||||
.option("--webgl-renderer <renderer>", "WebGL renderer string")
|
.option("--webgl-renderer <renderer>", "WebGL renderer string")
|
||||||
|
|
||||||
|
// Fingerprint
|
||||||
|
.option(
|
||||||
|
"--fingerprint <fingerprint>",
|
||||||
|
"custom BrowserForge fingerprint (JSON string)",
|
||||||
|
)
|
||||||
|
|
||||||
// Proxy
|
// Proxy
|
||||||
.option(
|
.option(
|
||||||
"--proxy <proxy>",
|
"--proxy <proxy>",
|
||||||
@@ -239,7 +245,7 @@ program
|
|||||||
// Firefox preferences
|
// Firefox preferences
|
||||||
.option("--firefox-prefs <prefs>", "Firefox user preferences (JSON string)")
|
.option("--firefox-prefs <prefs>", "Firefox user preferences (JSON string)")
|
||||||
|
|
||||||
.description("launch and manage Camoufox anti-detect browser instances")
|
.description("launch and manage Camoufox browser orchestrator instances")
|
||||||
.action(async (action: string, options: any) => {
|
.action(async (action: string, options: any) => {
|
||||||
try {
|
try {
|
||||||
if (action === "launch") {
|
if (action === "launch") {
|
||||||
@@ -264,6 +270,18 @@ program
|
|||||||
: options.os;
|
: 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
|
// Blocking options
|
||||||
if (options.blockImages) camoufoxOptions.block_images = true;
|
if (options.blockImages) camoufoxOptions.block_images = true;
|
||||||
if (options.blockWebrtc) camoufoxOptions.block_webrtc = true;
|
if (options.blockWebrtc) camoufoxOptions.block_webrtc = true;
|
||||||
@@ -272,20 +290,11 @@ program
|
|||||||
// Security options
|
// Security options
|
||||||
if (options.disableCoop) camoufoxOptions.disable_coop = true;
|
if (options.disableCoop) camoufoxOptions.disable_coop = true;
|
||||||
|
|
||||||
// Geolocation
|
// Geolocation IP
|
||||||
if (options.geoip) {
|
if (options.geoip) {
|
||||||
camoufoxOptions.geoip =
|
camoufoxOptions.geoip =
|
||||||
options.geoip === "auto" ? true : options.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
|
// UI and behavior
|
||||||
if (options.humanize) camoufoxOptions.humanize = options.humanize;
|
if (options.humanize) camoufoxOptions.humanize = options.humanize;
|
||||||
@@ -295,24 +304,40 @@ program
|
|||||||
if (options.locale) {
|
if (options.locale) {
|
||||||
camoufoxOptions.locale = options.locale.includes(",")
|
camoufoxOptions.locale = options.locale.includes(",")
|
||||||
? options.locale.split(",")
|
? options.locale.split(",")
|
||||||
: options.locale;
|
: [options.locale];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extensions and fonts
|
// Extensions and fonts
|
||||||
if (options.addons) camoufoxOptions.addons = options.addons.split(",");
|
if (options.addons) camoufoxOptions.addons = options.addons.split(",");
|
||||||
if (options.fonts) camoufoxOptions.fonts = options.fonts.split(",");
|
if (options.fonts) camoufoxOptions.fonts = options.fonts.split(",");
|
||||||
if (options.customFontsOnly) camoufoxOptions.custom_fonts_only = true;
|
if (options.customFontsOnly) camoufoxOptions.custom_fonts_only = true;
|
||||||
if (options.excludeAddons)
|
if (options.excludeAddons) {
|
||||||
camoufoxOptions.exclude_addons = options.excludeAddons.split(",");
|
// 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
|
// Screen dimensions - combine into screen object
|
||||||
const screen: any = {};
|
if (
|
||||||
if (options.screenMinWidth) screen.minWidth = options.screenMinWidth;
|
options.screenMinWidth ||
|
||||||
if (options.screenMaxWidth) screen.maxWidth = options.screenMaxWidth;
|
options.screenMaxWidth ||
|
||||||
if (options.screenMinHeight) screen.minHeight = options.screenMinHeight;
|
options.screenMinHeight ||
|
||||||
if (options.screenMaxHeight) screen.maxHeight = options.screenMaxHeight;
|
options.screenMaxHeight
|
||||||
if (Object.keys(screen).length > 0) camoufoxOptions.screen = screen;
|
) {
|
||||||
|
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) {
|
if (options.windowWidth && options.windowHeight) {
|
||||||
camoufoxOptions.window = [options.windowWidth, options.windowHeight];
|
camoufoxOptions.window = [options.windowWidth, options.windowHeight];
|
||||||
}
|
}
|
||||||
@@ -320,6 +345,8 @@ program
|
|||||||
// Advanced options
|
// Advanced options
|
||||||
if (options.ffVersion) camoufoxOptions.ff_version = options.ffVersion;
|
if (options.ffVersion) camoufoxOptions.ff_version = options.ffVersion;
|
||||||
if (options.mainWorldEval) camoufoxOptions.main_world_eval = true;
|
if (options.mainWorldEval) camoufoxOptions.main_world_eval = true;
|
||||||
|
|
||||||
|
// WebGL - combine vendor and renderer into webgl_config tuple
|
||||||
if (options.webglVendor && options.webglRenderer) {
|
if (options.webglVendor && options.webglRenderer) {
|
||||||
camoufoxOptions.webgl_config = [
|
camoufoxOptions.webgl_config = [
|
||||||
options.webglVendor,
|
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
|
// Proxy
|
||||||
if (options.proxy) camoufoxOptions.proxy = options.proxy;
|
if (options.proxy) camoufoxOptions.proxy = options.proxy;
|
||||||
|
|
||||||
@@ -389,16 +427,9 @@ program
|
|||||||
console.log(JSON.stringify({ success }));
|
console.log(JSON.stringify({ success }));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} else if (action === "list") {
|
} else if (action === "list") {
|
||||||
const processes = listCamoufoxProcesses();
|
const processes = await listCamoufoxProcesses();
|
||||||
// Convert camelCase to snake_case for Rust compatibility
|
// The processes already have snake_case properties, no conversion needed
|
||||||
const rustCompatibleProcesses = processes.map((process) => ({
|
console.log(JSON.stringify(processes));
|
||||||
id: process.id,
|
|
||||||
pid: process.pid,
|
|
||||||
executable_path: process.executablePath,
|
|
||||||
profile_path: process.profilePath,
|
|
||||||
url: process.url,
|
|
||||||
}));
|
|
||||||
console.log(JSON.stringify(rustCompatibleProcesses));
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} else if (action === "open-url") {
|
} else if (action === "open-url") {
|
||||||
if (!options.id || !options.url) {
|
if (!options.id || !options.url) {
|
||||||
|
|||||||
+1
-1
@@ -58,7 +58,7 @@
|
|||||||
"@biomejs/biome": "2.0.6",
|
"@biomejs/biome": "2.0.6",
|
||||||
"@tailwindcss/postcss": "^4.1.11",
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
"@tauri-apps/cli": "^2.6.2",
|
"@tauri-apps/cli": "^2.6.2",
|
||||||
"@types/node": "^24.0.10",
|
"@types/node": "^24.0.11",
|
||||||
"@types/react": "^19.1.8",
|
"@types/react": "^19.1.8",
|
||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
|
|||||||
Generated
+179
-1305
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "donutbrowser"
|
name = "donutbrowser"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
description = "Simple Yet Powerful Anti-Detect Browser"
|
description = "Simple Yet Powerful Browser Orchestrator"
|
||||||
authors = ["zhom@github"]
|
authors = ["zhom@github"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "donutbrowser"
|
default-run = "donutbrowser"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Version=1.0
|
Version=1.0
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=Donut Browser
|
Name=Donut Browser
|
||||||
Comment=Simple Yet Powerful Anti-Detect Browser
|
Comment=Simple Yet Powerful Browser Orchestrator
|
||||||
Exec=donutbrowser %u
|
Exec=donutbrowser %u
|
||||||
Icon=donutbrowser
|
Icon=donutbrowser
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ impl AppAutoUpdater {
|
|||||||
percentage: Some(percentage),
|
percentage: Some(percentage),
|
||||||
speed: Some(format!("{speed:.1}")),
|
speed: Some(format!("{speed:.1}")),
|
||||||
eta: Some(eta),
|
eta: Some(eta),
|
||||||
message: format!("Downloading update... {percentage:.1}%"),
|
message: "Downloading update...".to_string(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub struct CamoufoxConfig {
|
|||||||
pub screen_max_height: Option<u32>,
|
pub screen_max_height: Option<u32>,
|
||||||
pub window_width: Option<u32>,
|
pub window_width: Option<u32>,
|
||||||
pub window_height: Option<u32>,
|
pub window_height: Option<u32>,
|
||||||
|
pub fingerprint: Option<serde_json::Value>,
|
||||||
pub ff_version: Option<u32>,
|
pub ff_version: Option<u32>,
|
||||||
pub main_world_eval: Option<bool>,
|
pub main_world_eval: Option<bool>,
|
||||||
pub webgl_vendor: Option<String>,
|
pub webgl_vendor: Option<String>,
|
||||||
@@ -70,6 +71,7 @@ impl Default for CamoufoxConfig {
|
|||||||
screen_max_height: None,
|
screen_max_height: None,
|
||||||
window_width: None,
|
window_width: None,
|
||||||
window_height: None,
|
window_height: None,
|
||||||
|
fingerprint: None,
|
||||||
ff_version: None,
|
ff_version: None,
|
||||||
main_world_eval: None,
|
main_world_eval: None,
|
||||||
webgl_vendor: 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 {
|
if let Some(proxy) = &config.proxy {
|
||||||
sidecar = sidecar.arg("--proxy").arg(proxy);
|
sidecar = sidecar.arg("--proxy").arg(proxy);
|
||||||
}
|
}
|
||||||
@@ -457,9 +466,7 @@ impl CamoufoxLauncher {
|
|||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.or_else(|| process.get("profilePath").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)) =
|
if let Some(id) = id {
|
||||||
(id, executable_path, profile_path)
|
|
||||||
{
|
|
||||||
let pid = process
|
let pid = process
|
||||||
.get("pid")
|
.get("pid")
|
||||||
.and_then(|v| v.as_u64())
|
.and_then(|v| v.as_u64())
|
||||||
@@ -470,6 +477,10 @@ impl CamoufoxLauncher {
|
|||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.map(|s| s.to_string());
|
.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 {
|
results.push(CamoufoxLaunchResult {
|
||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
pid,
|
pid,
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ mod windows {
|
|||||||
app_key
|
app_key
|
||||||
.set_value(
|
.set_value(
|
||||||
"ApplicationDescription",
|
"ApplicationDescription",
|
||||||
&"Donut Browser - Simple Yet Powerful Anti-Detect Browser",
|
&"Donut Browser - Simple Yet Powerful Browser Orchestrator",
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("Failed to set ApplicationDescription: {}", e))?;
|
.map_err(|e| format!("Failed to set ApplicationDescription: {}", e))?;
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ mod windows {
|
|||||||
capabilities
|
capabilities
|
||||||
.set_value(
|
.set_value(
|
||||||
"ApplicationDescription",
|
"ApplicationDescription",
|
||||||
&"Donut Browser - Simple Yet Powerful Anti-Detect Browser",
|
&"Donut Browser - Simple Yet Powerful Browser Orchestrator",
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("Failed to set Capabilities description: {}", e))?;
|
.map_err(|e| format!("Failed to set Capabilities description: {}", e))?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user