feat: partially add wayfern

This commit is contained in:
zhom
2026-01-09 09:50:07 +04:00
parent fdd921c6bb
commit e9c084d6a4
22 changed files with 2313 additions and 127 deletions
+26 -2
View File
@@ -29,7 +29,7 @@ import { useProxyEvents } from "@/hooks/use-proxy-events";
import { useUpdateNotifications } from "@/hooks/use-update-notifications";
import { useVersionUpdater } from "@/hooks/use-version-updater";
import { showErrorToast, showSuccessToast, showToast } from "@/lib/toast-utils";
import type { BrowserProfile, CamoufoxConfig } from "@/types";
import type { BrowserProfile, CamoufoxConfig, WayfernConfig } from "@/types";
type BrowserTypeString =
| "firefox"
@@ -37,7 +37,8 @@ type BrowserTypeString =
| "chromium"
| "brave"
| "zen"
| "camoufox";
| "camoufox"
| "wayfern";
interface PendingUrl {
id: string;
@@ -387,6 +388,26 @@ export default function Home() {
[],
);
const handleSaveWayfernConfig = useCallback(
async (profile: BrowserProfile, config: WayfernConfig) => {
try {
await invoke("update_wayfern_config", {
profileId: profile.id,
config,
});
// No need to manually reload - useProfileEvents will handle the update
setCamoufoxConfigDialogOpen(false);
} catch (err: unknown) {
console.error("Failed to update wayfern config:", err);
showErrorToast(
`Failed to update wayfern config: ${JSON.stringify(err)}`,
);
throw err;
}
},
[],
);
const handleCreateProfile = useCallback(
async (profileData: {
name: string;
@@ -395,6 +416,7 @@ export default function Home() {
releaseType: string;
proxyId?: string;
camoufoxConfig?: CamoufoxConfig;
wayfernConfig?: WayfernConfig;
groupId?: string;
}) => {
try {
@@ -405,6 +427,7 @@ export default function Home() {
releaseType: profileData.releaseType,
proxyId: profileData.proxyId,
camoufoxConfig: profileData.camoufoxConfig,
wayfernConfig: profileData.wayfernConfig,
groupId:
profileData.groupId ||
(selectedGroupId !== "default" ? selectedGroupId : undefined),
@@ -834,6 +857,7 @@ export default function Home() {
}}
profile={currentProfileForCamoufoxConfig}
onSave={handleSaveCamoufoxConfig}
onSaveWayfern={handleSaveWayfernConfig}
isRunning={
currentProfileForCamoufoxConfig
? runningProfiles.has(currentProfileForCamoufoxConfig.id)
+33 -10
View File
@@ -28,6 +28,10 @@ interface CamoufoxConfigDialogProps {
onClose: () => void;
profile: BrowserProfile | null;
onSave: (profile: BrowserProfile, config: CamoufoxConfig) => Promise<void>;
onSaveWayfern?: (
profile: BrowserProfile,
config: CamoufoxConfig,
) => Promise<void>;
isRunning?: boolean;
}
@@ -36,6 +40,7 @@ export function CamoufoxConfigDialog({
onClose,
profile,
onSave,
onSaveWayfern,
isRunning = false,
}: CamoufoxConfigDialogProps) {
const [config, setConfig] = useState<CamoufoxConfig>(() => ({
@@ -44,17 +49,24 @@ export function CamoufoxConfigDialog({
}));
const [isSaving, setIsSaving] = useState(false);
const isAntiDetectBrowser =
profile?.browser === "camoufox" || profile?.browser === "wayfern";
// Initialize config when profile changes
useEffect(() => {
if (profile && profile.browser === "camoufox") {
if (profile && isAntiDetectBrowser) {
const profileConfig =
profile.browser === "wayfern"
? profile.wayfern_config
: profile.camoufox_config;
setConfig(
profile.camoufox_config || {
profileConfig || {
geoip: true,
os: getCurrentOS(),
},
);
}
}, [profile]);
}, [profile, isAntiDetectBrowser]);
const updateConfig = (key: keyof CamoufoxConfig, value: unknown) => {
setConfig((prev) => ({ ...prev, [key]: value }));
@@ -79,10 +91,14 @@ export function CamoufoxConfigDialog({
setIsSaving(true);
try {
await onSave(profile, config);
if (profile.browser === "wayfern" && onSaveWayfern) {
await onSaveWayfern(profile, config);
} else {
await onSave(profile, config);
}
onClose();
} catch (error) {
console.error("Failed to save camoufox config:", error);
console.error("Failed to save config:", error);
const { toast } = await import("sonner");
toast.error("Failed to save configuration", {
description:
@@ -95,9 +111,13 @@ export function CamoufoxConfigDialog({
const handleClose = () => {
// Reset config to original when closing without saving
if (profile && profile.browser === "camoufox") {
if (profile && isAntiDetectBrowser) {
const profileConfig =
profile.browser === "wayfern"
? profile.wayfern_config
: profile.camoufox_config;
setConfig(
profile.camoufox_config || {
profileConfig || {
geoip: true,
os: getCurrentOS(),
},
@@ -106,11 +126,11 @@ export function CamoufoxConfigDialog({
onClose();
};
if (!profile || profile.browser !== "camoufox") {
if (!profile || !isAntiDetectBrowser) {
return null;
}
// No OS warning needed anymore since we removed OS selection
const browserName = profile.browser === "wayfern" ? "Wayfern" : "Camoufox";
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
@@ -118,7 +138,7 @@ export function CamoufoxConfigDialog({
<DialogHeader className="shrink-0">
<DialogTitle>
{isRunning ? "View" : "Configure"} Fingerprint Settings -{" "}
{profile.name}
{profile.name} ({browserName})
</DialogTitle>
</DialogHeader>
@@ -129,6 +149,9 @@ export function CamoufoxConfigDialog({
onConfigChange={updateConfig}
forceAdvanced={true}
readOnly={isRunning}
browserType={
profile.browser === "wayfern" ? "wayfern" : "camoufox"
}
/>
</div>
</ScrollArea>
+209 -53
View File
@@ -24,12 +24,18 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Tabs, TabsContent } from "@/components/ui/tabs";
import { useBrowserDownload } from "@/hooks/use-browser-download";
import { useProxyEvents } from "@/hooks/use-proxy-events";
import { getBrowserIcon } from "@/lib/browser-utils";
import type { BrowserReleaseTypes, CamoufoxConfig, CamoufoxOS } from "@/types";
import type {
BrowserReleaseTypes,
CamoufoxConfig,
CamoufoxOS,
WayfernConfig,
WayfernOS,
} from "@/types";
const getCurrentOS = (): CamoufoxOS => {
if (typeof navigator === "undefined") return "linux";
@@ -47,7 +53,8 @@ type BrowserTypeString =
| "chromium"
| "brave"
| "zen"
| "camoufox";
| "camoufox"
| "wayfern";
interface CreateProfileDialogProps {
isOpen: boolean;
@@ -59,6 +66,7 @@ interface CreateProfileDialogProps {
releaseType: string;
proxyId?: string;
camoufoxConfig?: CamoufoxConfig;
wayfernConfig?: WayfernConfig;
groupId?: string;
}) => Promise<void>;
selectedGroupId?: string;
@@ -115,6 +123,11 @@ export function CreateProfileDialog({
os: getCurrentOS(), // Default to current OS
}));
// Wayfern anti-detect states
const [wayfernConfig, setWayfernConfig] = useState<WayfernConfig>(() => ({
os: getCurrentOS() as WayfernOS, // Default to current OS
}));
// Handle browser selection from the initial screen
const handleBrowserSelect = (browser: BrowserTypeString) => {
setSelectedBrowser(browser);
@@ -197,7 +210,7 @@ export function CreateProfileDialog({
// Only update state if this browser is still the one we're loading
if (loadingBrowserRef.current === browser) {
// Filter to enforce stable-only creation, except Firefox Developer (nightly-only)
if (browser === "camoufox") {
if (browser === "camoufox" || browser === "wayfern") {
const filtered: BrowserReleaseTypes = {};
if (rawReleaseTypes.stable)
filtered.stable = rawReleaseTypes.stable;
@@ -267,8 +280,8 @@ export function CreateProfileDialog({
if (selectedBrowser) {
void loadReleaseTypes(selectedBrowser);
}
// Check and download GeoIP database if needed for Camoufox
if (selectedBrowser === "camoufox") {
// Check and download GeoIP database if needed for Camoufox or Wayfern
if (selectedBrowser === "camoufox" || selectedBrowser === "wayfern") {
void checkAndDownloadGeoIPDatabase();
}
}
@@ -333,26 +346,50 @@ export function CreateProfileDialog({
setIsCreating(true);
try {
if (activeTab === "anti-detect") {
// Anti-detect browser - always use Camoufox with best available version
const bestCamoufoxVersion = getBestAvailableVersion("camoufox");
if (!bestCamoufoxVersion) {
console.error("No Camoufox version available");
return;
// Anti-detect browser - check if Wayfern or Camoufox is selected
if (selectedBrowser === "wayfern") {
const bestWayfernVersion = getBestAvailableVersion("wayfern");
if (!bestWayfernVersion) {
console.error("No Wayfern version available");
return;
}
// The fingerprint will be generated at launch time by the Rust backend
const finalWayfernConfig = { ...wayfernConfig };
await onCreateProfile({
name: profileName.trim(),
browserStr: "wayfern" as BrowserTypeString,
version: bestWayfernVersion.version,
releaseType: bestWayfernVersion.releaseType,
proxyId: selectedProxyId,
wayfernConfig: finalWayfernConfig,
groupId:
selectedGroupId !== "default" ? selectedGroupId : undefined,
});
} else {
// Default to Camoufox
const bestCamoufoxVersion = getBestAvailableVersion("camoufox");
if (!bestCamoufoxVersion) {
console.error("No Camoufox version available");
return;
}
// The fingerprint will be generated at launch time by the Rust backend
// We don't need to generate it here during profile creation
const finalCamoufoxConfig = { ...camoufoxConfig };
await onCreateProfile({
name: profileName.trim(),
browserStr: "camoufox" as BrowserTypeString,
version: bestCamoufoxVersion.version,
releaseType: bestCamoufoxVersion.releaseType,
proxyId: selectedProxyId,
camoufoxConfig: finalCamoufoxConfig,
groupId:
selectedGroupId !== "default" ? selectedGroupId : undefined,
});
}
// The fingerprint will be generated at launch time by the Rust backend
// We don't need to generate it here during profile creation
const finalCamoufoxConfig = { ...camoufoxConfig };
await onCreateProfile({
name: profileName.trim(),
browserStr: "camoufox" as BrowserTypeString,
version: bestCamoufoxVersion.version,
releaseType: bestCamoufoxVersion.releaseType,
proxyId: selectedProxyId,
camoufoxConfig: finalCamoufoxConfig,
groupId: selectedGroupId !== "default" ? selectedGroupId : undefined,
});
} else {
// Regular browser
if (!selectedBrowser) {
@@ -402,6 +439,9 @@ export function CreateProfileDialog({
geoip: true, // Reset to automatic geoip
os: getCurrentOS(), // Reset to current OS
});
setWayfernConfig({
os: getCurrentOS() as WayfernOS, // Reset to current OS
});
onClose();
};
@@ -409,6 +449,10 @@ export function CreateProfileDialog({
setCamoufoxConfig((prev) => ({ ...prev, [key]: value }));
};
const updateWayfernConfig = (key: keyof WayfernConfig, value: unknown) => {
setWayfernConfig((prev) => ({ ...prev, [key]: value }));
};
// Check if browser version is downloaded and available
const isBrowserVersionAvailable = useCallback(
(browserStr: string) => {
@@ -461,13 +505,7 @@ export function CreateProfileDialog({
onValueChange={handleTabChange}
className="flex flex-col flex-1 w-full min-h-0"
>
<TabsList
className="grid flex-shrink-0 grid-cols-2 w-full"
defaultValue="anti-detect"
>
<TabsTrigger value="anti-detect">Anti-Detect</TabsTrigger>
<TabsTrigger value="regular">Regular</TabsTrigger>
</TabsList>
{/* Tab list hidden - only anti-detect browsers are supported */}
<ScrollArea className="overflow-y-auto flex-1">
<div className="flex flex-col justify-center items-center w-full">
@@ -482,30 +520,60 @@ export function CreateProfileDialog({
Anti-Detect Browser
</h3>
<p className="mt-2 text-sm text-muted-foreground">
Choose Firefox for anti-detection capabilities
Choose a browser with anti-detection capabilities
</p>
</div>
<Button
onClick={() => handleBrowserSelect("camoufox")}
className="flex gap-3 justify-start items-center p-4 w-full h-16 border-2 transition-colors hover:border-primary/50"
variant="outline"
>
<div className="flex justify-center items-center w-8 h-8">
{(() => {
const IconComponent = getBrowserIcon("firefox");
return IconComponent ? (
<IconComponent className="w-6 h-6" />
) : null;
})()}
</div>
<div className="text-left">
<div className="font-medium">Firefox</div>
<div className="text-sm text-muted-foreground">
Anti-Detect Browser
<div className="space-y-3">
{/* Wayfern (Chromium) - First */}
<Button
onClick={() => handleBrowserSelect("wayfern")}
className="flex gap-3 justify-start items-center p-4 w-full h-16 border-2 transition-colors hover:border-primary/50"
variant="outline"
>
<div className="flex justify-center items-center w-8 h-8">
{(() => {
const IconComponent = getBrowserIcon("wayfern");
return IconComponent ? (
<IconComponent className="w-6 h-6" />
) : null;
})()}
</div>
</div>
</Button>
<div className="text-left">
<div className="font-medium">
Chromium (Wayfern)
</div>
<div className="text-sm text-muted-foreground">
Anti-Detect Browser
</div>
</div>
</Button>
{/* Camoufox (Firefox) - Second */}
<Button
onClick={() => handleBrowserSelect("camoufox")}
className="flex gap-3 justify-start items-center p-4 w-full h-16 border-2 transition-colors hover:border-primary/50"
variant="outline"
>
<div className="flex justify-center items-center w-8 h-8">
{(() => {
const IconComponent =
getBrowserIcon("camoufox");
return IconComponent ? (
<IconComponent className="w-6 h-6" />
) : null;
})()}
</div>
<div className="text-left">
<div className="font-medium">
Firefox (Camoufox)
</div>
<div className="text-sm text-muted-foreground">
Anti-Detect Browser
</div>
</div>
</Button>
</div>
</div>
</TabsContent>
@@ -570,7 +638,94 @@ export function CreateProfileDialog({
/>
</div>
{selectedBrowser === "camoufox" ? (
{selectedBrowser === "wayfern" ? (
// Wayfern Configuration
<div className="space-y-6">
{/* Wayfern Download Status */}
{isLoadingReleaseTypes && (
<div className="flex gap-3 items-center p-3 rounded-md border">
<div className="w-4 h-4 rounded-full border-2 animate-spin border-muted/40 border-t-primary" />
<p className="text-sm text-muted-foreground">
Fetching available versions...
</p>
</div>
)}
{!isLoadingReleaseTypes && releaseTypesError && (
<div className="flex gap-3 items-center p-3 rounded-md border border-destructive/50 bg-destructive/10">
<p className="flex-1 text-sm text-destructive">
{releaseTypesError}
</p>
<RippleButton
onClick={() =>
selectedBrowser &&
loadReleaseTypes(selectedBrowser)
}
size="sm"
variant="outline"
>
Retry
</RippleButton>
</div>
)}
{!isLoadingReleaseTypes &&
!releaseTypesError &&
!isBrowserCurrentlyDownloading("wayfern") &&
!isBrowserVersionAvailable("wayfern") &&
getBestAvailableVersion("wayfern") && (
<div className="flex gap-3 items-center p-3 rounded-md border">
<p className="text-sm text-muted-foreground">
{(() => {
const bestVersion =
getBestAvailableVersion("wayfern");
return `Wayfern version (${bestVersion?.version}) needs to be downloaded`;
})()}
</p>
<LoadingButton
onClick={() => handleDownload("wayfern")}
isLoading={isBrowserCurrentlyDownloading(
"wayfern",
)}
size="sm"
disabled={isBrowserCurrentlyDownloading(
"wayfern",
)}
>
{isBrowserCurrentlyDownloading("wayfern")
? "Downloading..."
: "Download"}
</LoadingButton>
</div>
)}
{!isLoadingReleaseTypes &&
!releaseTypesError &&
!isBrowserCurrentlyDownloading("wayfern") &&
isBrowserVersionAvailable("wayfern") && (
<div className="p-3 text-sm rounded-md border text-muted-foreground">
{(() => {
const bestVersion =
getBestAvailableVersion("wayfern");
return `✓ Wayfern version (${bestVersion?.version}) is available`;
})()}
</div>
)}
{isBrowserCurrentlyDownloading("wayfern") && (
<div className="p-3 text-sm rounded-md border text-muted-foreground">
{(() => {
const bestVersion =
getBestAvailableVersion("wayfern");
return `Downloading Wayfern version (${bestVersion?.version})...`;
})()}
</div>
)}
<SharedCamoufoxConfigForm
config={wayfernConfig}
onConfigChange={updateWayfernConfig}
isCreating
browserType="wayfern"
/>
</div>
) : selectedBrowser === "camoufox" ? (
// Camoufox Configuration
<div className="space-y-6">
{/* Camoufox Download Status */}
@@ -654,6 +809,7 @@ export function CreateProfileDialog({
config={camoufoxConfig}
onConfigChange={updateCamoufoxConfig}
isCreating
browserType="camoufox"
/>
</div>
) : (
+2 -1
View File
@@ -1999,7 +1999,8 @@ export function ProfilesDataTable({
>
Assign to Group
</DropdownMenuItem>
{profile.browser === "camoufox" &&
{(profile.browser === "camoufox" ||
profile.browser === "wayfern") &&
meta.onConfigureCamoufox && (
<DropdownMenuItem
onClick={() => {
+38 -34
View File
@@ -28,6 +28,7 @@ interface SharedCamoufoxConfigFormProps {
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
}
// Determine if fingerprint editing should be disabled
@@ -116,6 +117,7 @@ export function SharedCamoufoxConfigForm({
isCreating = false,
forceAdvanced = false,
readOnly = false,
browserType = "camoufox",
}: SharedCamoufoxConfigFormProps) {
const [activeTab, setActiveTab] = useState(
forceAdvanced ? "manual" : "automatic",
@@ -282,42 +284,44 @@ export function SharedCamoufoxConfigForm({
)}
<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>
{/* Blocking Options - Only available for Camoufox */}
{browserType === "camoufox" && (
<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>
</div>
)}
{/* Navigator Properties */}
<div className="space-y-3">
+10 -15
View File
@@ -3,9 +3,7 @@
* Centralized helpers for browser name mapping, icons, etc.
*/
import { FaChrome, FaFirefox, FaShieldAlt } from "react-icons/fa";
import { SiBrave } from "react-icons/si";
import { ZenBrowser } from "@/components/icons/zen-browser";
import { FaChrome, FaExclamationTriangle, FaFirefox } from "react-icons/fa";
/**
* Map internal browser names to display names
@@ -17,7 +15,8 @@ export function getBrowserDisplayName(browserType: string): string {
zen: "Zen Browser",
brave: "Brave",
chromium: "Chromium",
camoufox: "Anti-Detect",
camoufox: "Firefox (Camoufox)",
wayfern: "Chromium (Wayfern)",
};
return browserNames[browserType] || browserType;
@@ -25,22 +24,18 @@ export function getBrowserDisplayName(browserType: string): string {
/**
* Get the appropriate icon component for a browser type
* Anti-detect browsers get their base browser icons
* Other browsers get a warning icon to indicate they're not anti-detect
*/
export function getBrowserIcon(browserType: string) {
switch (browserType) {
case "chromium":
return FaChrome;
case "brave":
return SiBrave;
case "firefox":
case "firefox-developer":
return FaFirefox;
case "zen":
return ZenBrowser;
case "camoufox":
return FaShieldAlt;
return FaFirefox; // Firefox-based anti-detect browser
case "wayfern":
return FaChrome; // Chromium-based anti-detect browser
default:
return null;
// All other browsers get a warning icon
return FaExclamationTriangle;
}
}
+27
View File
@@ -21,6 +21,7 @@ export interface BrowserProfile {
last_launch?: number;
release_type: string; // "stable" or "nightly"
camoufox_config?: CamoufoxConfig; // Camoufox configuration
wayfern_config?: WayfernConfig; // Wayfern configuration
group_id?: string; // Reference to profile group
tags?: string[];
note?: string; // User note
@@ -289,6 +290,32 @@ export interface CamoufoxLaunchResult {
url?: string;
}
export type WayfernOS = "windows" | "macos" | "linux";
export interface WayfernConfig {
proxy?: string;
screen_max_width?: number;
screen_max_height?: number;
screen_min_width?: number;
screen_min_height?: number;
geoip?: string | boolean; // For compatibility with shared config form
block_images?: boolean; // For compatibility with shared config form
block_webrtc?: boolean;
block_webgl?: boolean;
executable_path?: string;
fingerprint?: string; // JSON string of the complete fingerprint config
randomize_fingerprint_on_launch?: boolean; // Generate new fingerprint on every launch
os?: WayfernOS; // Operating system for fingerprint generation
}
export interface WayfernLaunchResult {
id: string;
processId?: number;
profilePath?: string;
url?: string;
cdp_port?: number;
}
// Traffic stats types
export interface BandwidthDataPoint {
timestamp: number;