mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-01 12:51:34 +02:00
feat: partially add wayfern
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -1999,7 +1999,8 @@ export function ProfilesDataTable({
|
||||
>
|
||||
Assign to Group
|
||||
</DropdownMenuItem>
|
||||
{profile.browser === "camoufox" &&
|
||||
{(profile.browser === "camoufox" ||
|
||||
profile.browser === "wayfern") &&
|
||||
meta.onConfigureCamoufox && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user