mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-12 17:57:50 +02:00
1120 lines
38 KiB
TypeScript
1120 lines
38 KiB
TypeScript
"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>
|
||
);
|
||
}
|