Files
donutbrowser/src/components/shared-camoufox-config-form.tsx
T

1120 lines
38 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import MultipleSelector, { type Option } from "@/components/multiple-selector";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import type {
CamoufoxConfig,
CamoufoxFingerprintConfig,
CamoufoxOS,
} from "@/types";
interface SharedCamoufoxConfigFormProps {
config: CamoufoxConfig;
onConfigChange: (key: keyof CamoufoxConfig, value: unknown) => void;
className?: string;
isCreating?: boolean; // Flag to indicate if this is for creating a new profile
forceAdvanced?: boolean; // Force advanced mode (for editing)
readOnly?: boolean; // Flag to indicate if the form should be read-only
}
// Determine if fingerprint editing should be disabled
const isFingerprintEditingDisabled = (config: CamoufoxConfig): boolean => {
return config.randomize_fingerprint_on_launch === true;
};
// Detect the current operating system
const getCurrentOS = (): CamoufoxOS => {
if (typeof navigator === "undefined") return "linux";
const platform = navigator.platform.toLowerCase();
if (platform.includes("win")) return "windows";
if (platform.includes("mac")) return "macos";
return "linux";
};
// OS display labels
const osLabels: Record<CamoufoxOS, string> = {
windows: "Windows",
macos: "macOS",
linux: "Linux",
};
// Component for editing nested objects like webGl:parameters
interface ObjectEditorProps {
value: Record<string, unknown> | undefined;
onChange: (value: Record<string, unknown> | undefined) => void;
title: string;
readOnly?: boolean;
}
function ObjectEditor({
value,
onChange,
title,
readOnly = false,
}: ObjectEditorProps) {
const [jsonString, setJsonString] = useState("");
useEffect(() => {
setJsonString(JSON.stringify(value || {}, null, 2));
}, [value]);
const handleChange = (newValue: string) => {
if (readOnly) return;
setJsonString(newValue);
try {
if (newValue.trim() === "" || newValue.trim() === "{}") {
onChange(undefined); // Treat empty objects as undefined
return;
}
const parsed = JSON.parse(newValue);
if (
typeof parsed === "object" &&
parsed !== null &&
Object.keys(parsed).length === 0
) {
onChange(undefined);
return;
}
onChange(parsed as Record<string, unknown>);
} catch (err) {
console.warn("Invalid JSON:", err);
}
};
return (
<div className="space-y-2">
<Label>{title}</Label>
<Textarea
value={jsonString}
onChange={(e) => handleChange(e.target.value)}
placeholder={`Enter ${title} as JSON`}
className="font-mono text-sm"
rows={6}
disabled={readOnly}
/>
</div>
);
}
export function SharedCamoufoxConfigForm({
config,
onConfigChange,
className = "",
isCreating = false,
forceAdvanced = false,
readOnly = false,
}: SharedCamoufoxConfigFormProps) {
const [activeTab, setActiveTab] = useState(
forceAdvanced ? "manual" : "automatic",
);
const [fingerprintConfig, setFingerprintConfig] =
useState<CamoufoxFingerprintConfig>({});
const [currentOS] = useState<CamoufoxOS>(getCurrentOS);
// Get selected OS (defaults to current OS)
const selectedOS = config.os || currentOS;
const isOSDifferent = selectedOS !== currentOS;
// Set screen resolution to user's screen size when creating a new profile
useEffect(() => {
if (isCreating && typeof window !== "undefined") {
const screenWidth = window.screen.width;
const screenHeight = window.screen.height;
// Only set if not already configured
if (!config.screen_max_width) {
onConfigChange("screen_max_width", screenWidth);
}
if (!config.screen_max_height) {
onConfigChange("screen_max_height", screenHeight);
}
}
}, [
isCreating,
config.screen_max_width,
config.screen_max_height,
onConfigChange,
]);
// Parse fingerprint config when component mounts or config changes
useEffect(() => {
if (config.fingerprint) {
try {
const parsed = JSON.parse(
config.fingerprint,
) as CamoufoxFingerprintConfig;
setFingerprintConfig(parsed);
} catch (error) {
console.error("Failed to parse fingerprint config:", error);
setFingerprintConfig({});
}
} else {
// Initialize with empty config if no fingerprint is set
setFingerprintConfig({});
}
}, [config.fingerprint]);
// Update fingerprint config and serialize it
const updateFingerprintConfig = (
key: keyof CamoufoxFingerprintConfig,
value: unknown,
) => {
const newConfig = { ...fingerprintConfig };
// Remove undefined values to keep the config clean
if (
value === undefined ||
value === "" ||
(Array.isArray(value) && value.length === 0)
) {
delete newConfig[key];
} else {
(newConfig as Record<string, unknown>)[key] = value;
}
setFingerprintConfig(newConfig);
// Validate that the config can be serialized to JSON
try {
const jsonString = JSON.stringify(newConfig);
onConfigChange("fingerprint", jsonString);
} catch (error) {
console.error("Failed to serialize fingerprint config:", error);
// Don't update if serialization fails
}
};
// Determine if automatic location configuration is enabled
const isAutoLocationEnabled = config.geoip !== false;
// Handle automatic location configuration toggle
const handleAutoLocationToggle = (enabled: boolean) => {
if (enabled) {
onConfigChange("geoip", true);
} else {
onConfigChange("geoip", false);
}
};
const isEditingDisabled = isFingerprintEditingDisabled(config) || readOnly;
const renderAdvancedForm = () => (
<div className="space-y-6">
{/* Operating System Selection */}
<div className="space-y-3">
<Label>Operating System Fingerprint</Label>
<Select
value={selectedOS}
onValueChange={(value: CamoufoxOS) => onConfigChange("os", value)}
disabled={readOnly}
>
<SelectTrigger>
<SelectValue placeholder="Select operating system" />
</SelectTrigger>
<SelectContent>
<SelectItem value="windows">{osLabels.windows}</SelectItem>
<SelectItem value="macos">{osLabels.macos}</SelectItem>
<SelectItem value="linux">{osLabels.linux}</SelectItem>
</SelectContent>
</Select>
{isOSDifferent && (
<Alert className="border-yellow-500/50 bg-yellow-500/10">
<AlertDescription className="text-yellow-600 dark:text-yellow-400">
Warning: Selecting an OS different from your current system (
{osLabels[currentOS]}) increases the risk of detection. Websites
can detect mismatches between your fingerprint and actual system
behavior.
</AlertDescription>
</Alert>
)}
</div>
{/* Randomize Fingerprint Option */}
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
<div className="flex items-center space-x-2">
<Checkbox
id="randomize-fingerprint"
checked={config.randomize_fingerprint_on_launch || false}
onCheckedChange={(checked) =>
onConfigChange("randomize_fingerprint_on_launch", checked)
}
disabled={readOnly}
/>
<Label htmlFor="randomize-fingerprint" className="font-medium">
Generate random fingerprint on every launch
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
When enabled, a new fingerprint will be generated each time the
browser is launched.
</p>
</div>
{isEditingDisabled ? (
<Alert>
<AlertDescription>
{readOnly
? "Fingerprint editing is disabled because the profile is currently running. Stop the profile to make changes."
: "Fingerprint editing is disabled because random fingerprint generation is enabled. Disable the option above to manually edit the fingerprint configuration."}
</AlertDescription>
</Alert>
) : (
<Alert>
<AlertDescription>
Warning: Only edit these parameters if you know what you're doing.
Incorrect values may break websites, make them detect you, and lead
to hard-to-debug bugs.{" "}
</AlertDescription>
</Alert>
)}
<fieldset disabled={isEditingDisabled} className="space-y-6">
{/* Blocking Options */}
<div className="space-y-3">
<Label>Blocking Options</Label>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Checkbox
id="block-images"
checked={config.block_images || false}
onCheckedChange={(checked) =>
onConfigChange("block_images", checked)
}
/>
<Label htmlFor="block-images">Block Images</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="block-webrtc"
checked={config.block_webrtc || false}
onCheckedChange={(checked) =>
onConfigChange("block_webrtc", checked)
}
/>
<Label htmlFor="block-webrtc">Block WebRTC</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="block-webgl"
checked={config.block_webgl || false}
onCheckedChange={(checked) =>
onConfigChange("block_webgl", checked)
}
/>
<Label htmlFor="block-webgl">Block WebGL</Label>
</div>
</div>
</div>
{/* Navigator Properties */}
<div className="space-y-3">
<Label>Navigator Properties</Label>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="user-agent">User Agent</Label>
<Input
id="user-agent"
value={fingerprintConfig["navigator.userAgent"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"navigator.userAgent",
e.target.value || undefined,
)
}
placeholder="Mozilla/5.0..."
/>
</div>
<div className="space-y-2">
<Label htmlFor="platform">Platform</Label>
<Input
id="platform"
value={fingerprintConfig["navigator.platform"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"navigator.platform",
e.target.value || undefined,
)
}
placeholder="e.g., MacIntel, Win32"
/>
</div>
<div className="space-y-2">
<Label htmlFor="app-version">App Version</Label>
<Input
id="app-version"
value={fingerprintConfig["navigator.appVersion"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"navigator.appVersion",
e.target.value || undefined,
)
}
placeholder="e.g., 5.0 (Macintosh)"
/>
</div>
<div className="space-y-2">
<Label htmlFor="oscpu">OS CPU</Label>
<Input
id="oscpu"
value={fingerprintConfig["navigator.oscpu"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"navigator.oscpu",
e.target.value || undefined,
)
}
placeholder="e.g., Intel Mac OS X 10.15"
/>
</div>
<div className="space-y-2">
<Label htmlFor="hardware-concurrency">Hardware Concurrency</Label>
<Input
id="hardware-concurrency"
type="number"
value={fingerprintConfig["navigator.hardwareConcurrency"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"navigator.hardwareConcurrency",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 8"
/>
</div>
<div className="space-y-2">
<Label htmlFor="max-touch-points">Max Touch Points</Label>
<Input
id="max-touch-points"
type="number"
value={fingerprintConfig["navigator.maxTouchPoints"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"navigator.maxTouchPoints",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 0"
/>
</div>
<div className="space-y-2">
<Label htmlFor="do-not-track">Do Not Track</Label>
<Select
value={fingerprintConfig["navigator.doNotTrack"] || ""}
onValueChange={(value) =>
updateFingerprintConfig(
"navigator.doNotTrack",
value || undefined,
)
}
>
<SelectTrigger>
<SelectValue placeholder="Select DNT value" />
</SelectTrigger>
<SelectContent>
<SelectItem value="0">0 (tracking allowed)</SelectItem>
<SelectItem value="1">1 (tracking not allowed)</SelectItem>
<SelectItem value="unspecified">unspecified</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="language">Language</Label>
<Input
id="language"
value={fingerprintConfig["navigator.language"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"navigator.language",
e.target.value || undefined,
)
}
placeholder="e.g., en-US"
/>
</div>
</div>
</div>
{/* Screen Properties */}
<div className="space-y-3">
<Label>Screen Properties</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="screen-width">Screen Width</Label>
<Input
id="screen-width"
type="number"
value={fingerprintConfig["screen.width"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"screen.width",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 1920"
/>
</div>
<div className="space-y-2">
<Label htmlFor="screen-height">Screen Height</Label>
<Input
id="screen-height"
type="number"
value={fingerprintConfig["screen.height"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"screen.height",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 1080"
/>
</div>
<div className="space-y-2">
<Label htmlFor="avail-width">Available Width</Label>
<Input
id="avail-width"
type="number"
value={fingerprintConfig["screen.availWidth"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"screen.availWidth",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 1920"
/>
</div>
<div className="space-y-2">
<Label htmlFor="avail-height">Available Height</Label>
<Input
id="avail-height"
type="number"
value={fingerprintConfig["screen.availHeight"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"screen.availHeight",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 1055"
/>
</div>
<div className="space-y-2">
<Label htmlFor="color-depth">Color Depth</Label>
<Input
id="color-depth"
type="number"
value={fingerprintConfig["screen.colorDepth"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"screen.colorDepth",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 30"
/>
</div>
<div className="space-y-2">
<Label htmlFor="pixel-depth">Pixel Depth</Label>
<Input
id="pixel-depth"
type="number"
value={fingerprintConfig["screen.pixelDepth"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"screen.pixelDepth",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 30"
/>
</div>
</div>
</div>
{/* Window Properties */}
<div className="space-y-3">
<Label>Window Properties</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="outer-width">Outer Width</Label>
<Input
id="outer-width"
type="number"
value={fingerprintConfig["window.outerWidth"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"window.outerWidth",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 1512"
/>
</div>
<div className="space-y-2">
<Label htmlFor="outer-height">Outer Height</Label>
<Input
id="outer-height"
type="number"
value={fingerprintConfig["window.outerHeight"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"window.outerHeight",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 886"
/>
</div>
<div className="space-y-2">
<Label htmlFor="inner-width">Inner Width</Label>
<Input
id="inner-width"
type="number"
value={fingerprintConfig["window.innerWidth"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"window.innerWidth",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 1512"
/>
</div>
<div className="space-y-2">
<Label htmlFor="inner-height">Inner Height</Label>
<Input
id="inner-height"
type="number"
value={fingerprintConfig["window.innerHeight"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"window.innerHeight",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 886"
/>
</div>
<div className="space-y-2">
<Label htmlFor="screen-x">Screen X</Label>
<Input
id="screen-x"
type="number"
value={fingerprintConfig["window.screenX"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"window.screenX",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 0"
/>
</div>
<div className="space-y-2">
<Label htmlFor="screen-y">Screen Y</Label>
<Input
id="screen-y"
type="number"
value={fingerprintConfig["window.screenY"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"window.screenY",
e.target.value ? parseInt(e.target.value, 10) : undefined,
)
}
placeholder="e.g., 0"
/>
</div>
</div>
</div>
{/* Geolocation */}
<div className="space-y-3">
<Label>Geolocation</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="latitude">Latitude</Label>
<Input
id="latitude"
type="number"
step="any"
value={fingerprintConfig["geolocation:latitude"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"geolocation:latitude",
e.target.value ? parseFloat(e.target.value) : undefined,
)
}
placeholder="e.g., 41.0019"
/>
</div>
<div className="space-y-2">
<Label htmlFor="longitude">Longitude</Label>
<Input
id="longitude"
type="number"
step="any"
value={fingerprintConfig["geolocation:longitude"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"geolocation:longitude",
e.target.value ? parseFloat(e.target.value) : undefined,
)
}
placeholder="e.g., 28.9645"
/>
</div>
<div className="space-y-2">
<Label htmlFor="timezone">Timezone</Label>
<Input
id="timezone"
type="text"
value={fingerprintConfig.timezone || ""}
onChange={(e) =>
updateFingerprintConfig(
"timezone",
e.target.value || undefined,
)
}
placeholder="e.g., America/New_York"
/>
</div>
</div>
</div>
{/* Locale */}
<div className="space-y-3">
<Label>Locale</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="locale-language">Language</Label>
<Input
id="locale-language"
value={fingerprintConfig["locale:language"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"locale:language",
e.target.value || undefined,
)
}
placeholder="e.g., tr"
/>
</div>
<div className="space-y-2">
<Label htmlFor="locale-region">Region</Label>
<Input
id="locale-region"
value={fingerprintConfig["locale:region"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"locale:region",
e.target.value || undefined,
)
}
placeholder="e.g., TR"
/>
</div>
<div className="space-y-2">
<Label htmlFor="locale-script">Script</Label>
<Input
id="locale-script"
value={fingerprintConfig["locale:script"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"locale:script",
e.target.value || undefined,
)
}
placeholder="e.g., Latn"
/>
</div>
</div>
</div>
{/* WebGL Properties */}
<div className="space-y-3">
<Label>WebGL Properties</Label>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="webgl-vendor">WebGL Vendor</Label>
<Input
id="webgl-vendor"
value={fingerprintConfig["webGl:vendor"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"webGl:vendor",
e.target.value || undefined,
)
}
placeholder="e.g., Mesa"
/>
</div>
<div className="space-y-2">
<Label htmlFor="webgl-renderer">WebGL Renderer</Label>
<Input
id="webgl-renderer"
value={fingerprintConfig["webGl:renderer"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"webGl:renderer",
e.target.value || undefined,
)
}
placeholder="e.g., llvmpipe, or similar"
/>
</div>
</div>
</div>
{/* WebGL Parameters */}
<div className="space-y-3">
<ObjectEditor
value={
(fingerprintConfig["webGl:parameters"] as Record<
string,
unknown
>) || {}
}
onChange={(value) =>
updateFingerprintConfig("webGl:parameters", value)
}
title="WebGL Parameters"
readOnly={readOnly}
/>
</div>
{/* WebGL2 Parameters */}
<div className="space-y-3">
<ObjectEditor
value={
(fingerprintConfig["webGl2:parameters"] as Record<
string,
unknown
>) || {}
}
onChange={(value) =>
updateFingerprintConfig("webGl2:parameters", value)
}
title="WebGL2 Parameters"
readOnly={readOnly}
/>
</div>
{/* WebGL Shader Precision Formats */}
<div className="space-y-3">
<ObjectEditor
value={
(fingerprintConfig["webGl:shaderPrecisionFormats"] as Record<
string,
unknown
>) || {}
}
onChange={(value) =>
updateFingerprintConfig("webGl:shaderPrecisionFormats", value)
}
title="WebGL Shader Precision Formats"
readOnly={readOnly}
/>
</div>
{/* WebGL2 Shader Precision Formats */}
<div className="space-y-3">
<ObjectEditor
value={
(fingerprintConfig["webGl2:shaderPrecisionFormats"] as Record<
string,
unknown
>) || {}
}
onChange={(value) =>
updateFingerprintConfig("webGl2:shaderPrecisionFormats", value)
}
title="WebGL2 Shader Precision Formats"
readOnly={readOnly}
/>
</div>
{/* Fonts */}
<div className="space-y-3">
<Label>Fonts</Label>
<MultipleSelector
value={
fingerprintConfig.fonts?.map((font) => ({
label: font,
value: font,
})) || []
}
onChange={(selected: Option[]) =>
updateFingerprintConfig(
"fonts",
selected.map((s: Option) => s.value),
)
}
placeholder="Add fonts..."
creatable
/>
</div>
{/* Battery */}
<div className="space-y-3">
<Label>Battery</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Checkbox
id="battery-charging"
checked={fingerprintConfig["battery:charging"] || false}
onCheckedChange={(checked) =>
updateFingerprintConfig("battery:charging", checked)
}
/>
<Label htmlFor="battery-charging">Charging</Label>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="charging-time">Charging Time</Label>
<Input
id="charging-time"
type="number"
step="any"
value={fingerprintConfig["battery:chargingTime"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"battery:chargingTime",
e.target.value ? parseFloat(e.target.value) : undefined,
)
}
placeholder="e.g., 0"
/>
</div>
<div className="space-y-2">
<Label htmlFor="discharging-time">Discharging Time</Label>
<Input
id="discharging-time"
type="number"
step="any"
value={fingerprintConfig["battery:dischargingTime"] || ""}
onChange={(e) =>
updateFingerprintConfig(
"battery:dischargingTime",
e.target.value ? parseFloat(e.target.value) : undefined,
)
}
placeholder="e.g., 0"
/>
</div>
</div>
</div>
{/* Browser Behavior */}
{/* <div className="space-y-3">
<Label>Browser Behavior</Label>
<div className="flex items-center space-x-2">
<Checkbox
id="allow-addon-new-tab"
checked={fingerprintConfig.allowAddonNewTab}
onCheckedChange={(checked) =>
updateFingerprintConfig("allowAddonNewTab", checked)
}
/>
<Label htmlFor="allow-addon-new-tab">
Allow browser addons to open new tabs automatically
</Label>
</div>
</div> */}
</fieldset>
</div>
);
return (
<div className={`space-y-6 ${className}`}>
{forceAdvanced ? (
// Advanced mode only (for editing)
renderAdvancedForm()
) : (
<Tabs
value={activeTab}
onValueChange={readOnly ? undefined : setActiveTab}
className="w-full"
>
<TabsList className="grid grid-cols-2 w-full">
<TabsTrigger value="automatic" disabled={readOnly}>
Automatic
</TabsTrigger>
<TabsTrigger value="manual" disabled={readOnly}>
Manual
</TabsTrigger>
</TabsList>
<TabsContent value="automatic" className="space-y-6">
{/* Operating System Selection */}
<div className="mt-4 space-y-3">
<Label>Operating System Fingerprint</Label>
<Select
value={selectedOS}
onValueChange={(value: CamoufoxOS) =>
onConfigChange("os", value)
}
disabled={readOnly}
>
<SelectTrigger>
<SelectValue placeholder="Select operating system" />
</SelectTrigger>
<SelectContent>
<SelectItem value="windows">{osLabels.windows}</SelectItem>
<SelectItem value="macos">{osLabels.macos}</SelectItem>
<SelectItem value="linux">{osLabels.linux}</SelectItem>
</SelectContent>
</Select>
{isOSDifferent && (
<Alert className="border-yellow-500/50 bg-yellow-500/10">
<AlertDescription className="text-yellow-600 dark:text-yellow-400">
Warning: Selecting an OS different from your current
system ({osLabels[currentOS]}) increases the risk of
detection. Websites with advanced protections can detect
mismatches between your fingerprint and actual system
behavior.
</AlertDescription>
</Alert>
)}
</div>
{/* Randomize Fingerprint Option */}
<div className="space-y-3 p-4 border rounded-lg bg-muted/30">
<div className="flex items-center space-x-2">
<Checkbox
id="randomize-fingerprint-auto"
checked={config.randomize_fingerprint_on_launch || false}
onCheckedChange={(checked) =>
onConfigChange("randomize_fingerprint_on_launch", checked)
}
disabled={readOnly}
/>
<Label
htmlFor="randomize-fingerprint-auto"
className="font-medium"
>
Generate random fingerprint on every launch
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
When enabled, a new fingerprint will be generated each time the
browser is launched. The generated fingerprint is saved for
reference.
</p>
</div>
{/* Automatic Location Configuration */}
<div className="space-y-3">
<div className="flex items-center space-x-2">
<Checkbox
id="auto-location"
checked={isAutoLocationEnabled}
onCheckedChange={handleAutoLocationToggle}
disabled={isEditingDisabled}
/>
<Label htmlFor="auto-location">
Automatically configure location information based on proxy
configuration or your connection if no proxy provided
</Label>
</div>
</div>
{/* Screen Resolution */}
<fieldset disabled={isEditingDisabled} className="space-y-3">
<Label>Screen Resolution</Label>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="screen-max-width">Max Width</Label>
<Input
id="screen-max-width"
type="number"
value={config.screen_max_width || ""}
onChange={(e) =>
onConfigChange(
"screen_max_width",
e.target.value
? parseInt(e.target.value, 10)
: undefined,
)
}
placeholder="e.g., 1920"
/>
</div>
<div className="space-y-2">
<Label htmlFor="screen-max-height">Max Height</Label>
<Input
id="screen-max-height"
type="number"
value={config.screen_max_height || ""}
onChange={(e) =>
onConfigChange(
"screen_max_height",
e.target.value
? parseInt(e.target.value, 10)
: undefined,
)
}
placeholder="e.g., 1080"
/>
</div>
<div className="space-y-2">
<Label htmlFor="screen-min-width">Min Width</Label>
<Input
id="screen-min-width"
type="number"
value={config.screen_min_width || ""}
onChange={(e) =>
onConfigChange(
"screen_min_width",
e.target.value
? parseInt(e.target.value, 10)
: undefined,
)
}
placeholder="e.g., 800"
/>
</div>
<div className="space-y-2">
<Label htmlFor="screen-min-height">Min Height</Label>
<Input
id="screen-min-height"
type="number"
value={config.screen_min_height || ""}
onChange={(e) =>
onConfigChange(
"screen_min_height",
e.target.value
? parseInt(e.target.value, 10)
: undefined,
)
}
placeholder="e.g., 600"
/>
</div>
</div>
</fieldset>
</TabsContent>
<TabsContent value="manual" className="space-y-6">
{renderAdvancedForm()}
</TabsContent>
</Tabs>
)}
</div>
);
}