checkpoint

This commit is contained in:
zhom
2025-08-06 20:43:14 +04:00
parent 5159f943df
commit a461fd4798
13 changed files with 301 additions and 204 deletions
+14 -9
View File
@@ -1,7 +1,7 @@
import { spawn } from "node:child_process";
import path from "node:path";
import { launchOptions } from "camoufox-js";
import type { LaunchOptions } from "camoufox-js/dist/utils.js";
import { launchOptions } from "donutbrowser-camoufox-js";
import type { LaunchOptions } from "donutbrowser-camoufox-js/dist/utils.js";
import {
type CamoufoxConfig,
deleteCamoufoxConfig,
@@ -196,7 +196,7 @@ function convertCamoufoxToFingerprintGenerator(
fingerprintObj.fonts = camoufoxFingerprint.fonts;
}
return fingerprintObj;
return { ...camoufoxFingerprint, ...fingerprintObj };
}
/**
@@ -465,7 +465,7 @@ export async function generateCamoufoxConfig(
options: GenerateConfigOptions,
): Promise<string> {
try {
const launchOpts: LaunchOptions = {
const launchOpts: any = {
headless: false,
i_know_what_im_doing: true,
config: {
@@ -474,8 +474,8 @@ export async function generateCamoufoxConfig(
},
};
if (options.geoip !== undefined) {
launchOpts.geoip = options.geoip;
if (options.geoip) {
launchOpts.geoip = true;
}
if (options.blockImages) {
@@ -492,11 +492,19 @@ export async function generateCamoufoxConfig(
launchOpts.executable_path = options.executablePath;
}
if (options.proxy) {
launchOpts.proxy = options.proxy;
}
// If fingerprint is provided, use it and ignore other options except executable_path and block_*
if (options.fingerprint) {
try {
const camoufoxFingerprint = JSON.parse(options.fingerprint);
if (camoufoxFingerprint.timezone) {
launchOpts.config.timezone = camoufoxFingerprint.timezone;
}
// Convert camoufox fingerprint format to fingerprint-generator format
const fingerprintObj =
convertCamoufoxToFingerprintGenerator(camoufoxFingerprint);
@@ -506,9 +514,6 @@ export async function generateCamoufoxConfig(
}
} else {
// Use individual options to build configuration
if (options.proxy) {
launchOpts.proxy = options.proxy;
}
if (options.maxWidth && options.maxHeight) {
launchOpts.screen = {
+1 -1
View File
@@ -1,6 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import type { LaunchOptions } from "camoufox-js/dist/utils.js";
import type { LaunchOptions } from "donutbrowser-camoufox-js/dist/utils.js";
import tmp from "tmp";
export interface CamoufoxConfig {
+21 -5
View File
@@ -1,8 +1,8 @@
import { launchOptions } from "camoufox-js";
import type { LaunchOptions } from "camoufox-js/dist/utils.js";
import { launchOptions } from "donutbrowser-camoufox-js";
import type { LaunchOptions } from "donutbrowser-camoufox-js/dist/utils.js";
import { type Browser, type BrowserServer, firefox } from "playwright-core";
import { getCamoufoxConfig, saveCamoufoxConfig } from "./camoufox-storage.js";
import { getEnvVars } from "./utils.js";
import { getEnvVars, parseProxyString } from "./utils.js";
/**
* Run a Camoufox browser server as a worker process
@@ -128,15 +128,31 @@ export async function runCamoufoxWorker(id: string): Promise<void> {
"Failed to parse custom config, using generated config:",
error,
);
return;
}
}
// Launch the server with the final configuration
server = await firefox.launchServer({
const finalOptions: any = {
...generatedOptions,
wsPath: `/ws_${config.id}`,
env: finalEnv,
});
};
// Only add proxy if it exists and is valid
if (camoufoxOptions.proxy) {
try {
finalOptions.proxy = parseProxyString(camoufoxOptions.proxy);
} catch (error) {
console.error({
message: "Failed to parse proxy, launching without proxy",
error,
});
return;
}
}
server = await firefox.launchServer(finalOptions);
// Connect to the server
browser = await firefox.connect(server.wsEndpoint());
+16 -29
View File
@@ -1,5 +1,5 @@
import type { LaunchOptions } from "camoufox-js/dist/utils.js";
import { program } from "commander";
import type { LaunchOptions } from "donutbrowser-camoufox-js/dist/utils.js";
import {
generateCamoufoxConfig,
startCamoufoxProcess,
@@ -165,7 +165,7 @@ program
.option("--proxy <proxy>", "proxy URL for config generation")
.option("--max-width <width>", "maximum screen width", parseInt)
.option("--max-height <height>", "maximum screen height", parseInt)
.option("--geoip [ip]", "enable geoip or specify IP")
.option("--geoip", "enable geoip")
.option("--block-images", "block images")
.option("--block-webrtc", "block WebRTC")
.option("--block-webgl", "block WebGL")
@@ -325,7 +325,6 @@ program
}),
);
process.exit(1);
return;
}
}
@@ -374,22 +373,6 @@ program
process.exit(0);
} else if (action === "generate-config") {
try {
// Handle geoip option properly
let geoipValue: string | boolean = true; // Default to true
if (options.geoip !== undefined) {
if (typeof options.geoip === "boolean") {
geoipValue = options.geoip;
} else if (typeof options.geoip === "string") {
if (options.geoip === "true") {
geoipValue = true;
} else if (options.geoip === "false") {
geoipValue = false;
} else {
geoipValue = options.geoip; // IP address
}
}
}
const config = await generateCamoufoxConfig({
proxy:
typeof options.proxy === "string" ? options.proxy : undefined,
@@ -401,7 +384,7 @@ program
typeof options.maxHeight === "number"
? options.maxHeight
: undefined,
geoip: geoipValue,
geoip: Boolean(options.geoip),
blockImages:
typeof options.blockImages === "boolean"
? options.blockImages
@@ -426,17 +409,18 @@ program
console.log(config);
process.exit(0);
} catch (error: unknown) {
console.error(
`Failed to generate config: ${
error instanceof Error ? error.message : JSON.stringify(error)
}`,
);
console.error({
error: "Failed to generate config",
message:
error instanceof Error ? error.message : JSON.stringify(error),
});
process.exit(1);
}
} else {
console.error(
"Invalid action. Use 'start', 'stop', 'list', or 'generate-config'",
);
console.error({
error: "Invalid action",
message: "Use 'start', 'stop', 'list', or 'generate-config'",
});
process.exit(1);
}
},
@@ -452,7 +436,10 @@ program
if (action === "start") {
await runCamoufoxWorker(options.id);
} else {
console.error("Invalid action for camoufox-worker. Use 'start'");
console.error({
error: "Invalid action for camoufox-worker",
message: "Use 'start'",
});
process.exit(1);
}
});
+80
View File
@@ -1,3 +1,5 @@
import type { LaunchOptions } from "playwright-core";
const OS_MAP: { [key: string]: "mac" | "win" | "lin" } = {
darwin: "mac",
linux: "lin",
@@ -35,3 +37,81 @@ export function getEnvVars(configMap: Record<string, string>) {
return envVars;
}
export function parseProxyString(proxyString: LaunchOptions["proxy"] | string) {
if (typeof proxyString === "object") {
return proxyString;
}
if (!proxyString || typeof proxyString !== "string") {
throw new Error("Invalid proxy string provided");
}
// Remove any leading/trailing whitespace
const trimmed = proxyString.trim();
// Handle different proxy string formats:
// 1. http://username:password@host:port
// 2. host:port
// 3. protocol://host:port
// 4. username:password@host:port
let server = "";
let username: string | undefined;
let password: string | undefined;
try {
// Try parsing as URL first (handles protocol://username:password@host:port)
if (trimmed.includes("://")) {
const url = new URL(trimmed);
server = `${url.hostname}:${url.port}`;
if (url.username) {
username = decodeURIComponent(url.username);
}
if (url.password) {
password = decodeURIComponent(url.password);
}
} else {
// Handle formats without protocol
let workingString = trimmed;
// Check for username:password@ prefix
const authMatch = workingString.match(/^([^:@]+):([^@]+)@(.+)$/);
if (authMatch) {
username = authMatch[1];
password = authMatch[2];
workingString = authMatch[3];
}
// The remaining part should be host:port
server = workingString;
}
// Validate that we have a server
if (!server) {
throw new Error("Could not extract server information");
}
// Basic validation for host:port format
if (!server.includes(":") || server.split(":").length !== 2) {
throw new Error("Server must be in host:port format");
}
const result: LaunchOptions["proxy"] = { server };
if (username !== undefined) {
result.username = username;
}
if (password !== undefined) {
result.password = password;
}
return result;
} catch (error) {
throw new Error(
`Failed to parse proxy string: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
}