Files
donutbrowser/src/components/shared-camoufox-config-form.tsx
T
2026-03-09 14:24:18 +04:00

1324 lines
47 KiB
TypeScript

"use client";
import { invoke } from "@tauri-apps/api/core";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { LoadingButton } from "@/components/loading-button";
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 { ProBadge } from "@/components/ui/pro-badge";
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
browserType?: "camoufox" | "wayfern"; // Browser type to customize form options
crossOsUnlocked?: boolean; // Allow selecting non-current OS (paid feature)
limitedMode?: boolean; // Blur and disable advanced fields while keeping basic options accessible
profileVersion?: string;
profileBrowser?: string;
}
// 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,
browserType = "camoufox",
crossOsUnlocked = false,
limitedMode = false,
profileVersion,
profileBrowser,
}: SharedCamoufoxConfigFormProps) {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState(
forceAdvanced ? "manual" : "automatic",
);
const [fingerprintConfig, setFingerprintConfig] =
useState<CamoufoxFingerprintConfig>({});
const [currentOS] = useState<CamoufoxOS>(getCurrentOS);
const [isGeneratingFingerprint, setIsGeneratingFingerprint] = useState(false);
const handleGenerateFingerprint = async () => {
if (!profileVersion) return;
const browser = profileBrowser || browserType || "camoufox";
setIsGeneratingFingerprint(true);
try {
const configJson = JSON.stringify(config);
const result = await invoke<string>("generate_sample_fingerprint", {
browser,
version: profileVersion,
configJson,
});
onConfigChange("fingerprint", result);
} catch (error) {
console.error("Failed to generate fingerprint:", error);
} finally {
setIsGeneratingFingerprint(false);
}
};
// Get selected OS (defaults to current OS)
const selectedOS = config.os || 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">
<div className="flex items-center justify-between">
<Label>{t("fingerprint.osLabel")}</Label>
{profileVersion && (!isCreating || crossOsUnlocked) && (
<LoadingButton
isLoading={isGeneratingFingerprint}
onClick={handleGenerateFingerprint}
disabled={readOnly}
variant="outline"
size="sm"
>
{isCreating
? t("fingerprint.generateFingerprint")
: t("fingerprint.refreshFingerprint")}
</LoadingButton>
)}
</div>
<Select
value={selectedOS}
onValueChange={(value: CamoufoxOS) => onConfigChange("os", value)}
disabled={readOnly}
>
<SelectTrigger>
<SelectValue placeholder={t("fingerprint.selectOSPlaceholder")} />
</SelectTrigger>
<SelectContent>
{(["windows", "macos", "linux"] as CamoufoxOS[]).map((os) => {
const isDisabled = os !== currentOS && !crossOsUnlocked;
return (
<SelectItem key={os} value={os} disabled={isDisabled}>
<span className="flex items-center gap-2">
{osLabels[os]}
{isDisabled && <ProBadge />}
</span>
</SelectItem>
);
})}
</SelectContent>
</Select>
{selectedOS !== currentOS && crossOsUnlocked && (
<Alert className="mt-2">
<AlertDescription>
{t("fingerprint.crossOsWarning")}
</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">
{t("fingerprint.generateRandomOnLaunch")}
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
{t("fingerprint.generateRandomDescription")}
</p>
</div>
{/* Automatic Location Configuration */}
<div className="space-y-3">
<div className="flex items-center space-x-2">
<Checkbox
id="auto-location-advanced"
checked={isAutoLocationEnabled}
onCheckedChange={handleAutoLocationToggle}
disabled={readOnly}
/>
<Label htmlFor="auto-location-advanced">
{t("fingerprint.autoLocationDescription")}
</Label>
</div>
</div>
<div
className={
limitedMode ? "relative overflow-hidden rounded-lg" : undefined
}
>
{!limitedMode &&
(isEditingDisabled ? (
<Alert>
<AlertDescription>
{readOnly
? t("fingerprint.editingDisabledRunning")
: t("fingerprint.editingDisabledRandomized")}
</AlertDescription>
</Alert>
) : (
<Alert>
<AlertDescription>
{t("fingerprint.advancedWarning")}
</AlertDescription>
</Alert>
))}
<fieldset
disabled={isEditingDisabled || limitedMode}
className="space-y-6"
>
{/* Blocking Options - Only available for Camoufox */}
{browserType === "camoufox" && (
<div className="space-y-3">
<Label>{t("fingerprint.blockingOptions")}</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">
{t("fingerprint.blockImages")}
</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">
{t("fingerprint.blockWebRTC")}
</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">
{t("fingerprint.blockWebGL")}
</Label>
</div>
</div>
</div>
)}
{/* Navigator Properties */}
<div className="space-y-3">
<Label>{t("fingerprint.navigatorProperties")}</Label>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="user-agent">{t("fingerprint.userAgent")}</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">{t("fingerprint.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">
{t("fingerprint.appVersion")}
</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">{t("fingerprint.osCpu")}</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">
{t("fingerprint.hardwareConcurrency")}
</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">
{t("fingerprint.maxTouchPoints")}
</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">
{t("fingerprint.doNotTrack")}
</Label>
<Select
value={fingerprintConfig["navigator.doNotTrack"] || ""}
onValueChange={(value) =>
updateFingerprintConfig(
"navigator.doNotTrack",
value || undefined,
)
}
>
<SelectTrigger>
<SelectValue
placeholder={t("fingerprint.selectDntPlaceholder")}
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="0">
{t("fingerprint.dntAllowed")}
</SelectItem>
<SelectItem value="1">
{t("fingerprint.dntNotAllowed")}
</SelectItem>
<SelectItem value="unspecified">
{t("fingerprint.dntUnspecified")}
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="language">{t("fingerprint.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>{t("fingerprint.screenProperties")}</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="screen-width">
{t("fingerprint.screenWidth")}
</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">
{t("fingerprint.screenHeight")}
</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">
{t("fingerprint.availableWidth")}
</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">
{t("fingerprint.availableHeight")}
</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">
{t("fingerprint.colorDepth")}
</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">
{t("fingerprint.pixelDepth")}
</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>{t("fingerprint.windowProperties")}</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="outer-width">
{t("fingerprint.outerWidth")}
</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">
{t("fingerprint.outerHeight")}
</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">
{t("fingerprint.innerWidth")}
</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">
{t("fingerprint.innerHeight")}
</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">{t("fingerprint.screenX")}</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">{t("fingerprint.screenY")}</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>{t("fingerprint.geolocation")}</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="latitude">{t("fingerprint.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">{t("fingerprint.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">{t("fingerprint.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>{t("fingerprint.locale")}</Label>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2">
<Label htmlFor="locale-language">
{t("fingerprint.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">{t("fingerprint.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">{t("fingerprint.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>{t("fingerprint.webglProperties")}</Label>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="webgl-vendor">
{t("fingerprint.webglVendor")}
</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">
{t("fingerprint.webglRenderer")}
</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={t("fingerprint.webglParameters")}
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={t("fingerprint.webgl2Parameters")}
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={t("fingerprint.webglShaderPrecisionFormats")}
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={t("fingerprint.webgl2ShaderPrecisionFormats")}
readOnly={readOnly}
/>
</div>
{/* Fonts */}
<div className="space-y-3">
<Label>{t("fingerprint.fonts")}</Label>
<MultipleSelector
value={(() => {
// Handle fonts being either an array or a JSON string (Wayfern format)
let fontsArray: string[] = [];
if (fingerprintConfig.fonts) {
if (Array.isArray(fingerprintConfig.fonts)) {
fontsArray = fingerprintConfig.fonts;
} else if (typeof fingerprintConfig.fonts === "string") {
try {
const parsed = JSON.parse(fingerprintConfig.fonts);
if (Array.isArray(parsed)) {
fontsArray = parsed;
}
} catch {
// Invalid JSON, ignore
}
}
}
return fontsArray.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>{t("fingerprint.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">
{t("fingerprint.charging")}
</Label>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="charging-time">
{t("fingerprint.chargingTime")}
</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">
{t("fingerprint.dischargingTime")}
</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>
{limitedMode && (
<>
<div className="absolute inset-0 backdrop-blur-[6px] bg-background/30 z-[1]" />
<div className="absolute inset-y-0 left-0 w-6 bg-gradient-to-r from-background to-transparent z-[2]" />
<div className="absolute inset-y-0 right-0 w-6 bg-gradient-to-l from-background to-transparent z-[2]" />
<div className="absolute inset-x-0 top-0 h-6 bg-gradient-to-b from-background to-transparent z-[2]" />
<div className="absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-background to-transparent z-[2]" />
<div className="absolute inset-0 flex items-center justify-center z-[3]">
<div className="flex items-center gap-2 rounded-md bg-background/80 px-3 py-1.5">
<ProBadge />
<span className="text-sm font-medium text-muted-foreground">
{t("fingerprint.proFeature")}
</span>
</div>
</div>
</>
)}
</div>
</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}>
{t("fingerprint.automatic")}
</TabsTrigger>
<TabsTrigger value="manual" disabled={readOnly}>
{t("fingerprint.manual")}
</TabsTrigger>
</TabsList>
<TabsContent value="automatic" className="space-y-6">
{/* Operating System Selection */}
<div className="mt-4 space-y-3">
<Label>{t("fingerprint.osLabel")}</Label>
<Select
value={selectedOS}
onValueChange={(value: CamoufoxOS) =>
onConfigChange("os", value)
}
disabled={readOnly}
>
<SelectTrigger>
<SelectValue
placeholder={t("fingerprint.selectOSPlaceholder")}
/>
</SelectTrigger>
<SelectContent>
{(["windows", "macos", "linux"] as CamoufoxOS[]).map((os) => {
const isDisabled = os !== currentOS && !crossOsUnlocked;
return (
<SelectItem key={os} value={os} disabled={isDisabled}>
<span className="flex items-center gap-2">
{osLabels[os]}
{isDisabled && <ProBadge />}
</span>
</SelectItem>
);
})}
</SelectContent>
</Select>
{selectedOS !== currentOS && crossOsUnlocked && (
<Alert className="mt-2">
<AlertDescription>
{t("fingerprint.crossOsLimitations")}
</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"
>
{t("fingerprint.generateRandomOnLaunch")}
</Label>
</div>
<p className="text-sm text-muted-foreground ml-6">
{t("fingerprint.generateRandomDescriptionAuto")}
</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">
{t("fingerprint.autoLocationDescription")}
</Label>
</div>
</div>
{/* Screen Resolution */}
<div
className={
limitedMode ? "relative overflow-hidden rounded-lg" : undefined
}
>
<fieldset
disabled={isEditingDisabled || limitedMode}
className="space-y-3"
>
<Label>{t("fingerprint.screenResolution")}</Label>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="screen-max-width">
{t("fingerprint.maxWidth")}
</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">
{t("fingerprint.maxHeight")}
</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">
{t("fingerprint.minWidth")}
</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">
{t("fingerprint.minHeight")}
</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>
{limitedMode && (
<>
<div className="absolute inset-0 backdrop-blur-[6px] bg-background/30 z-[1]" />
<div className="absolute inset-y-0 left-0 w-6 bg-gradient-to-r from-background to-transparent z-[2]" />
<div className="absolute inset-y-0 right-0 w-6 bg-gradient-to-l from-background to-transparent z-[2]" />
<div className="absolute inset-x-0 top-0 h-6 bg-gradient-to-b from-background to-transparent z-[2]" />
<div className="absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-background to-transparent z-[2]" />
<div className="absolute inset-0 flex items-center justify-center z-[3]">
<div className="flex items-center gap-2 rounded-md bg-background/80 px-3 py-1.5">
<ProBadge />
<span className="text-sm font-medium text-muted-foreground">
{t("fingerprint.proFeature")}
</span>
</div>
</div>
</>
)}
</div>
</TabsContent>
<TabsContent value="manual" className="space-y-6">
{renderAdvancedForm()}
</TabsContent>
</Tabs>
)}
</div>
);
}