mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-29 15:26:05 +02:00
refactor: switch nodecar to commonjs
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
||||
23
|
||||
24
|
||||
|
||||
|
||||
Vendored
+4
@@ -17,6 +17,7 @@
|
||||
"clippy",
|
||||
"cmdk",
|
||||
"codegen",
|
||||
"codesign",
|
||||
"CTYPE",
|
||||
"datareporting",
|
||||
"devedition",
|
||||
@@ -72,7 +73,9 @@
|
||||
"oscpu",
|
||||
"peerconnection",
|
||||
"pixbuf",
|
||||
"pkgman",
|
||||
"plasmohq",
|
||||
"postject",
|
||||
"prefs",
|
||||
"propertylist",
|
||||
"reqwest",
|
||||
@@ -113,6 +116,7 @@
|
||||
"urlencoding",
|
||||
"vercel",
|
||||
"VERYSILENT",
|
||||
"virtdisplay",
|
||||
"webgl",
|
||||
"webrtc",
|
||||
"winreg",
|
||||
|
||||
+17
-8
@@ -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,7 @@
|
||||
"dependencies": {
|
||||
"@types/node": "^24.0.10",
|
||||
"@yao-pkg/pkg": "^6.5.1",
|
||||
"camoufox-js": "^0.6.0",
|
||||
"camoufox-js": "file:../../camoufox-js",
|
||||
"commander": "^14.0.0",
|
||||
"dotenv": "^17.0.1",
|
||||
"get-port": "^7.1.0",
|
||||
@@ -35,5 +36,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/tmp": "^0.2.6"
|
||||
},
|
||||
"pkg": {
|
||||
"assets": [
|
||||
"node_modules/camoufox-js/**/*"
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/camoufox-js/**/*.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+124
-494
@@ -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,422 +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 Camoufox configuration object from launch options
|
||||
* This follows the complete Camoufox schema for CAMOU_CONFIG_* environment variables
|
||||
*/
|
||||
function createCamoufoxConfig(options: CamoufoxLaunchOptions): any {
|
||||
const config: any = {};
|
||||
|
||||
// Debug flag
|
||||
if (options.debug !== undefined) {
|
||||
config.debug = options.debug;
|
||||
}
|
||||
|
||||
// Locale settings - parse locale string into language and region
|
||||
if (options.locale) {
|
||||
const localeValue = Array.isArray(options.locale)
|
||||
? options.locale[0]
|
||||
: options.locale;
|
||||
|
||||
// Parse locale like "en-US" into language and region
|
||||
const localeParts = localeValue.split("-");
|
||||
if (localeParts.length >= 2) {
|
||||
config["locale:language$__LOCALE"] = localeParts[0];
|
||||
config["locale:region$__LOCALE"] = localeParts[1];
|
||||
} else {
|
||||
config["locale:language$__LOCALE"] = localeParts[0];
|
||||
// Default region if not specified
|
||||
config["locale:region$__LOCALE"] = "US";
|
||||
}
|
||||
|
||||
// Set navigator language properties
|
||||
config["navigator.language"] = localeValue;
|
||||
config["navigator.languages"] = Array.isArray(options.locale)
|
||||
? options.locale
|
||||
: [localeValue];
|
||||
config["headers.Accept-Language"] = localeValue;
|
||||
config["locale:all"] = localeValue;
|
||||
}
|
||||
|
||||
// Screen dimensions from screen options
|
||||
if (options.screen) {
|
||||
if (options.screen.maxWidth) {
|
||||
config["screen.width$__SC"] = options.screen.maxWidth;
|
||||
config["screen.availWidth$__SC"] = options.screen.maxWidth;
|
||||
}
|
||||
if (options.screen.maxHeight) {
|
||||
config["screen.height$__SC"] = options.screen.maxHeight;
|
||||
config["screen.availHeight$__SC"] = options.screen.maxHeight;
|
||||
}
|
||||
|
||||
// Set default screen properties if not specified
|
||||
if (!options.screen.maxWidth) {
|
||||
config["screen.width$__SC"] = 1920;
|
||||
config["screen.availWidth$__SC"] = 1920;
|
||||
}
|
||||
if (!options.screen.maxHeight) {
|
||||
config["screen.height$__SC"] = 1080;
|
||||
config["screen.availHeight$__SC"] = 1080;
|
||||
}
|
||||
|
||||
// Default screen position and color depth
|
||||
config["screen.availTop"] = 0;
|
||||
config["screen.availLeft"] = 0;
|
||||
config["screen.colorDepth"] = 24;
|
||||
config["screen.pixelDepth"] = 24;
|
||||
} else {
|
||||
// Default screen settings if not specified
|
||||
config["screen.width$__SC"] = 1920;
|
||||
config["screen.height$__SC"] = 1080;
|
||||
config["screen.availWidth$__SC"] = 1920;
|
||||
config["screen.availHeight$__SC"] = 1080;
|
||||
config["screen.availTop"] = 0;
|
||||
config["screen.availLeft"] = 0;
|
||||
config["screen.colorDepth"] = 24;
|
||||
config["screen.pixelDepth"] = 24;
|
||||
}
|
||||
|
||||
// Window dimensions
|
||||
if (options.window) {
|
||||
config["window.outerWidth$__W_OUTER"] = options.window[0];
|
||||
config["window.outerHeight$__W_OUTER"] = options.window[1];
|
||||
config["window.innerWidth$__W_INNER"] = options.window[0] - 16; // Account for scrollbars
|
||||
config["window.innerHeight$__W_INNER"] = options.window[1] - 100; // Account for browser chrome
|
||||
} else {
|
||||
// Default window dimensions
|
||||
config["window.outerWidth$__W_OUTER"] = 1280;
|
||||
config["window.outerHeight$__W_OUTER"] = 720;
|
||||
config["window.innerWidth$__W_INNER"] = 1264;
|
||||
config["window.innerHeight$__W_INNER"] = 620;
|
||||
}
|
||||
|
||||
// Window position and properties
|
||||
config["window.screenX"] = 0;
|
||||
config["window.screenY"] = 0;
|
||||
config["window.devicePixelRatio"] = 1.0;
|
||||
config["window.scrollMinX"] = 0;
|
||||
config["window.scrollMinY"] = 0;
|
||||
config["window.scrollMaxX"] = 0;
|
||||
config["window.scrollMaxY"] = 0;
|
||||
config["screen.pageXOffset"] = 0.0;
|
||||
config["screen.pageYOffset"] = 0.0;
|
||||
|
||||
// Document body dimensions
|
||||
config["document.body.clientWidth$__DOC_BODY"] =
|
||||
config["window.innerWidth$__W_INNER"];
|
||||
config["document.body.clientHeight$__DOC_BODY"] =
|
||||
config["window.innerHeight$__W_INNER"];
|
||||
config["document.body.clientTop"] = 0;
|
||||
config["document.body.clientLeft"] = 0;
|
||||
|
||||
// Geolocation
|
||||
if (options.geolocation) {
|
||||
config["geolocation:latitude$__GEO"] = options.geolocation.latitude;
|
||||
config["geolocation:longitude$__GEO"] = options.geolocation.longitude;
|
||||
if (options.geolocation.accuracy) {
|
||||
config["geolocation:accuracy"] = options.geolocation.accuracy;
|
||||
}
|
||||
}
|
||||
|
||||
// Timezone
|
||||
if (options.timezone) {
|
||||
config.timezone = options.timezone;
|
||||
}
|
||||
|
||||
// User Agent based on OS option
|
||||
const osOption = Array.isArray(options.os) ? options.os[0] : options.os;
|
||||
let userAgent: string;
|
||||
let platform: string;
|
||||
let oscpu: string;
|
||||
let appVersion: string;
|
||||
|
||||
switch (osOption) {
|
||||
case "macos":
|
||||
userAgent =
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0";
|
||||
platform = "MacIntel";
|
||||
oscpu = "Intel Mac OS X 10.15";
|
||||
appVersion =
|
||||
"5.0 (Macintosh; Intel Mac OS X 10.15; rv:135.0) Gecko/20100101 Firefox/135.0";
|
||||
break;
|
||||
case "linux":
|
||||
userAgent =
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0";
|
||||
platform = "Linux x86_64";
|
||||
oscpu = "Linux x86_64";
|
||||
appVersion =
|
||||
"5.0 (X11; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0";
|
||||
break;
|
||||
case "windows":
|
||||
default:
|
||||
userAgent =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0";
|
||||
platform = "Win32";
|
||||
oscpu = "Windows NT 10.0; Win64; x64";
|
||||
appVersion =
|
||||
"5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0";
|
||||
break;
|
||||
}
|
||||
|
||||
config["navigator.userAgent"] = userAgent;
|
||||
config["navigator.appVersion"] = appVersion;
|
||||
config["navigator.platform"] = platform;
|
||||
config["navigator.oscpu"] = oscpu;
|
||||
config["headers.User-Agent"] = userAgent;
|
||||
|
||||
// Headers
|
||||
config["headers.Accept-Encoding"] = "gzip, deflate, br";
|
||||
|
||||
// Fonts
|
||||
if (options.fonts && options.fonts.length > 0) {
|
||||
config.fonts = options.fonts;
|
||||
}
|
||||
config["fonts:spacing_seed"] = 0;
|
||||
|
||||
// WebGL configuration
|
||||
if (
|
||||
options.webgl_config &&
|
||||
Array.isArray(options.webgl_config) &&
|
||||
options.webgl_config.length === 2
|
||||
) {
|
||||
config["webGl:vendor$__WEBGL"] = options.webgl_config[0];
|
||||
config["webGl:renderer$__WEBGL"] = options.webgl_config[1];
|
||||
}
|
||||
|
||||
// WebRTC IP spoofing from geoip
|
||||
if (
|
||||
options.geoip &&
|
||||
typeof options.geoip === "string" &&
|
||||
options.geoip !== "auto"
|
||||
) {
|
||||
if (options.geoip.includes(":")) {
|
||||
// IPv6
|
||||
config["webrtc:ipv6"] = options.geoip;
|
||||
config["webrtc:localipv6"] = options.geoip;
|
||||
} else {
|
||||
// IPv4
|
||||
config["webrtc:ipv4"] = options.geoip;
|
||||
config["webrtc:localipv4"] = options.geoip;
|
||||
}
|
||||
}
|
||||
|
||||
// Addons
|
||||
if (options.addons && options.addons.length > 0) {
|
||||
config.addons = options.addons;
|
||||
}
|
||||
|
||||
// Humanization
|
||||
if (options.humanize !== undefined) {
|
||||
config.humanize = !!options.humanize;
|
||||
if (typeof options.humanize === "number") {
|
||||
config["humanize:maxTime"] = options.humanize;
|
||||
config["humanize:minTime"] = 0.0;
|
||||
} else {
|
||||
config["humanize:maxTime"] = 5.0;
|
||||
config["humanize:minTime"] = 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor visibility
|
||||
config.showcursor = false;
|
||||
|
||||
// Advanced browser settings
|
||||
if (options.main_world_eval) {
|
||||
config.allowMainWorld = options.main_world_eval;
|
||||
}
|
||||
|
||||
config.forceScopeAccess = false;
|
||||
config.enableRemoteSubframes = false;
|
||||
config.disableTheming = false;
|
||||
config.memorysaver = false;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create minimal user.js for Firefox-specific settings that are not part of Camoufox fingerprint config
|
||||
*/
|
||||
function createMinimalUserJs(
|
||||
profilePath: string,
|
||||
options: CamoufoxLaunchOptions,
|
||||
): void {
|
||||
const preferences: string[] = [];
|
||||
|
||||
// Basic privacy settings
|
||||
preferences.push('user_pref("privacy.resistFingerprinting", false);'); // Let Camoufox handle fingerprinting
|
||||
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);',
|
||||
);
|
||||
}
|
||||
|
||||
// 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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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});`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache settings
|
||||
if (options.enable_cache === false) {
|
||||
preferences.push('user_pref("browser.cache.disk.enable", false);');
|
||||
preferences.push('user_pref("browser.cache.memory.enable", false);');
|
||||
}
|
||||
|
||||
// Write user.js file only if we have preferences to set
|
||||
if (preferences.length > 0) {
|
||||
const userJsPath = path.join(profilePath, "user.js");
|
||||
fs.writeFileSync(userJsPath, preferences.join("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Camoufox configuration via environment variables
|
||||
*/
|
||||
function setCamoufoxConfigEnv(config: any, env: NodeJS.ProcessEnv): void {
|
||||
const configJson = JSON.stringify(config);
|
||||
const chunkSize = os.platform() === "win32" ? 2047 : 32767;
|
||||
|
||||
// Clear any existing CAMOU_CONFIG_* variables
|
||||
for (const key in env) {
|
||||
if (key.startsWith("CAMOU_CONFIG_")) {
|
||||
delete env[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Split config into chunks
|
||||
const chunks: string[] = [];
|
||||
for (let i = 0; i < configJson.length; i += chunkSize) {
|
||||
chunks.push(configJson.slice(i, i + chunkSize));
|
||||
}
|
||||
|
||||
// Set environment variables (start from index 1 as expected by Camoufox)
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
env[`CAMOU_CONFIG_${i + 1}`] = chunks[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch Camoufox browser with specified options
|
||||
*/
|
||||
@@ -618,88 +208,128 @@ export async function launchCamoufox(
|
||||
fs.mkdirSync(profilePath, { recursive: true });
|
||||
}
|
||||
|
||||
// Create Camoufox configuration
|
||||
const camoufoxConfig = createCamoufoxConfig(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,
|
||||
});
|
||||
|
||||
// Create minimal user.js for Firefox-specific settings (proxy, blocking, etc.)
|
||||
createMinimalUserJs(profilePath, options);
|
||||
if (options.debug) {
|
||||
console.log(
|
||||
"Generated launch config:",
|
||||
JSON.stringify(launchConfig, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
// Build command line arguments
|
||||
const args = buildCamoufoxArgs(options, profilePath, url);
|
||||
// Extract the command line args and environment from the launch config
|
||||
const args = [
|
||||
"-profile",
|
||||
profilePath,
|
||||
"-no-remote",
|
||||
...(launchConfig.args || []),
|
||||
];
|
||||
|
||||
// Prepare environment variables
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
};
|
||||
// Add URL if provided
|
||||
if (url) {
|
||||
args.push(url);
|
||||
}
|
||||
|
||||
// Add custom environment variables from options, converting values to strings
|
||||
if (options.env) {
|
||||
for (const [key, value] of Object.entries(options.env)) {
|
||||
if (value !== undefined) {
|
||||
env[key] = String(value);
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Set Camoufox configuration via environment variables
|
||||
setCamoufoxConfigEnv(camoufoxConfig, env);
|
||||
|
||||
if (options.debug) {
|
||||
console.log(
|
||||
"Camoufox configuration:",
|
||||
JSON.stringify(camoufoxConfig, null, 2),
|
||||
);
|
||||
console.log(
|
||||
"Environment variables set:",
|
||||
Object.keys(env).filter((key) => key.startsWith("CAMOU_CONFIG_")),
|
||||
);
|
||||
}
|
||||
|
||||
// Handle virtual display
|
||||
if (options.virtual_display) {
|
||||
env.DISPLAY = options.virtual_display;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+58
-33
@@ -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,25 +290,12 @@ program
|
||||
// Security options
|
||||
if (options.disableCoop) camoufoxOptions.disable_coop = true;
|
||||
|
||||
// Geolocation
|
||||
// Geolocation IP
|
||||
if (options.geoip) {
|
||||
camoufoxOptions.geoip =
|
||||
options.geoip === "auto" ? true : options.geoip;
|
||||
}
|
||||
|
||||
// Combine latitude/longitude into geolocation object if both provided
|
||||
if (options.latitude && options.longitude) {
|
||||
camoufoxOptions.geolocation = {
|
||||
latitude: options.latitude,
|
||||
longitude: options.longitude,
|
||||
accuracy: 100,
|
||||
};
|
||||
}
|
||||
|
||||
// Set timezone and country only if explicitly provided
|
||||
if (options.country) camoufoxOptions.country = options.country;
|
||||
if (options.timezone) camoufoxOptions.timezone = options.timezone;
|
||||
|
||||
// UI and behavior
|
||||
if (options.humanize) camoufoxOptions.humanize = options.humanize;
|
||||
if (options.headless) camoufoxOptions.headless = true;
|
||||
@@ -306,24 +311,33 @@ program
|
||||
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(",");
|
||||
|
||||
// Screen dimensions - combine into screen object if any are provided
|
||||
const screenOptions: any = {};
|
||||
if (options.screenMinWidth)
|
||||
screenOptions.minWidth = options.screenMinWidth;
|
||||
if (options.screenMaxWidth)
|
||||
screenOptions.maxWidth = options.screenMaxWidth;
|
||||
if (options.screenMinHeight)
|
||||
screenOptions.minHeight = options.screenMinHeight;
|
||||
if (options.screenMaxHeight)
|
||||
screenOptions.maxHeight = options.screenMaxHeight;
|
||||
if (Object.keys(screenOptions).length > 0) {
|
||||
camoufoxOptions.screen = screenOptions;
|
||||
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"];
|
||||
}
|
||||
}
|
||||
|
||||
// Window dimensions - combine into window tuple if both provided
|
||||
// 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];
|
||||
}
|
||||
@@ -332,7 +346,7 @@ program
|
||||
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 both provided
|
||||
// WebGL - combine vendor and renderer into webgl_config tuple
|
||||
if (options.webglVendor && options.webglRenderer) {
|
||||
camoufoxOptions.webgl_config = [
|
||||
options.webglVendor,
|
||||
@@ -340,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;
|
||||
|
||||
@@ -402,7 +427,7 @@ program
|
||||
console.log(JSON.stringify({ success }));
|
||||
process.exit(0);
|
||||
} else if (action === "list") {
|
||||
const processes = listCamoufoxProcesses();
|
||||
const processes = await listCamoufoxProcesses();
|
||||
// The processes already have snake_case properties, no conversion needed
|
||||
console.log(JSON.stringify(processes));
|
||||
process.exit(0);
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@
|
||||
"@biomejs/biome": "2.0.6",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@tauri-apps/cli": "^2.6.2",
|
||||
"@types/node": "^24.0.10",
|
||||
"@types/node": "^24.0.11",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
|
||||
Generated
+189
-328
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user