import 'package:exceptions/exceptions.dart'; import 'package:fast_equatable/fast_equatable.dart'; class FingerprintOverrides with FastEquatable { static final pattern = RegExp('([+-])([a-zA-Z_][a-zA-Z0-9_]{1,64})'); final bool? allTargets; final Map targets; FingerprintOverrides(this.allTargets, this.targets); //Monitor https://searchfox.org/firefox-main/source/toolkit/components/resistfingerprinting/RFPTargetsDefault.inc FingerprintOverrides.defaults() : this(false, { 'CanvasRandomization': true, 'EfficientCanvasRandomization': true, 'FontVisibilityLangPack': true, 'JSMathFdlibm': true, 'ScreenAvailToResolution': true, 'NavigatorHWConcurrencyTiered': true, 'MaxTouchPointsCollapse': true, }); FingerprintOverrides.hardenedDefaults() : this(false, { 'TouchEvents': true, 'PointerEvents': true, 'KeyboardEvents': true, 'ScreenOrientation': true, 'SpeechSynthesis': true, 'CSSPrefersReducedMotion': true, 'CSSPrefersContrast': true, 'CanvasRandomization': true, 'CanvasExtractionFromThirdPartiesIsBlocked': true, 'JSLocale': true, 'NavigatorAppVersion': true, 'NavigatorBuildID': true, 'NavigatorHWConcurrency': true, 'NavigatorOscpu': true, 'NavigatorPlatform': true, 'NavigatorUserAgent': true, 'PointerId': true, 'StreamVideoFacingMode': true, 'JSDateTimeUTC': true, 'JSMathFdlibm': true, 'Gamepad': true, 'HttpUserAgent': true, 'WindowOuterSize': true, 'WindowScreenXY': true, 'WindowInnerScreenXY': true, 'ScreenPixelDepth': true, 'ScreenRect': true, 'ScreenAvailRect': true, 'VideoElementMozFrames': true, 'VideoElementMozFrameDelay': true, 'VideoElementPlaybackQuality': true, 'ReduceTimerPrecision': true, 'WidgetEvents': true, 'MediaDevices': true, 'MediaCapabilities': true, 'AudioSampleRate': true, 'NetworkConnection': true, 'WindowDevicePixelRatio': true, 'MouseEventScreenPoint': true, 'FontVisibilityBaseSystem': true, 'FontVisibilityLangPack': true, 'DeviceSensors': true, 'RoundWindowSize': true, 'UseStandinsForNativeColors': true, 'AudioContext': true, 'MediaError': true, 'DOMStyleOsxFontSmoothing': true, 'CSSDeviceSize': true, 'CSSColorInfo': true, 'CSSResolution': true, 'CSSPrefersReducedTransparency': true, 'CSSInvertedColors': true, 'CSSVideoDynamicRange': true, 'CSSPointerCapabilities': true, 'WebGLRenderCapability': true, 'WebGLRenderInfo': true, 'SiteSpecificZoom': true, 'FontVisibilityRestrictGenerics': true, 'WebVTT': true, 'WebGPULimits': true, 'WebGPUIsFallbackAdapter': true, 'WebGPUSubgroupSizes': true, 'JSLocalePrompt': true, 'ScreenAvailToResolution': true, 'UseHardcodedFontSubstitutes': true, 'DiskStorageLimit': true, 'WebCodecs': true, 'MaxTouchPoints': true, 'MaxTouchPointsCollapse': true, 'NavigatorHWConcurrencyTiered': true, }); static Result parse( String input, Set availableTargets, ) { final cleaned = input.replaceAll(RegExp(r'\s'), ''); if (cleaned.isEmpty) return Result.success(FingerprintOverrides(null, {})); bool? allTargets; final targets = {}; for (final word in cleaned.split(',')) { final match = pattern.firstMatch(word); if (match == null) { return Result.failure( const ErrorMessage(source: 'FpParser', message: 'Invalid Override'), ); } final enabled = match.group(1) != '-'; final name = match.group(2)!; if (name == 'AllTargets') { allTargets = enabled; continue; } if (!availableTargets.contains(name)) { return Result.failure( const ErrorMessage( source: 'FpParser', message: 'Invalid target name', ), ); } targets[name] = enabled; } return Result.success(FingerprintOverrides(allTargets, targets)); } @override String toString() { final sb = StringBuffer(); if (allTargets != null) { sb.write('${allTargets! ? '+' : '-'}AllTargets'); if (targets.isNotEmpty) { sb.write(','); } } sb.write( targets.entries.map((e) => (e.value ? '+' : '-') + e.key).join(','), ); return sb.toString(); } FingerprintOverrides copyWithAllTargetsEnabled(bool value) { return FingerprintOverrides( value, Map.fromEntries(targets.entries.where((e) => e.value != value)), ); } FingerprintOverrides copyWithTarget(String name, bool value) { if (value && allTargets == true) { return this; } return FingerprintOverrides(allTargets, {...targets, name: value}); } @override List get hashParameters => [allTargets, targets]; }