commit 448085c068edb2d682ac35c78e4e59a2dc49f048 Author: Lain Iwakura Date: Fri Aug 29 00:19:10 2025 +0300 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2d7a43 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +node_modules/ +dist/ +.svelte-kit/ +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +.DS_Store +*.tsbuildinfo diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..1efb3ee --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +shamefully-hoist=true +strict-peer-dependencies=false +auto-install-peers=true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8d66f7d --- /dev/null +++ b/LICENSE @@ -0,0 +1,2 @@ +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2005ed4 --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# WhatCanFingerprint + +[![License](https://img.shields.io/badge/license-0BSD-blue.svg)](LICENSE) +[![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![Svelte](https://img.shields.io/badge/Svelte-FF3E00?logo=svelte&logoColor=white)](https://svelte.dev/) +[![Vite](https://img.shields.io/badge/Vite-646CFF?logo=vite&logoColor=white)](https://vitejs.dev/) +[![Node.js](https://img.shields.io/badge/Node.js-18+-339933?logo=node.js&logoColor=white)](https://nodejs.org/) + +[![Privacy Research](https://img.shields.io/badge/Privacy-Research-green)](https://en.wikipedia.org/wiki/Device_fingerprint) + +A browser fingerprinting analysis tool. + +## Overview + +WhatCanFingerprint is a web application that demonstrates the extensive capabilities of browser fingerprinting. It showcases how much information can be gathered about a user's device, browser, and behavior without requiring any permissions or user interaction. + +## Features + +### Core Browser APIs +- **Basic Information** - User Agent, platform, vendor, cookie settings, DNT header +- **Display Properties** - Screen resolution, color depth, pixel ratio, orientation +- **Localization** - Timezone, date/time formatting, language preferences +- **Graphics Rendering** - Canvas 2D fingerprinting with geometric shapes and text +- **WebGL Analysis** - GPU vendor, renderer, supported extensions, parameters +- **Audio Context** - Sample rates, channel configuration, processing capabilities + +### Advanced Detection Methods +- **Font Detection** - Available system fonts through measurement techniques +- **Plugin Enumeration** - Installed browser plugins and extensions +- **Hardware Profiling** - CPU cores, memory, touch capabilities +- **Network Analysis** - Connection type, bandwidth estimation, RTT measurement +- **Battery API** - Charging status, level, time estimates +- **Media Devices** - Available cameras, microphones, speakers +- **Storage APIs** - Support for localStorage, sessionStorage, IndexedDB, WebSQL + +### Security & Privacy Analysis +- **Permissions** - Geolocation, notifications, push messaging capabilities +- **Security Headers** - Content Security Policy, referrer policy analysis +- **Detection Evasion** - Headless browser detection, automation signatures +- **Timing Attacks** - Performance-based fingerprinting techniques +- **Memory Pressure** - JavaScript heap analysis and garbage collection patterns + +### Behavioral Fingerprinting +- **Mouse Dynamics** - Movement patterns and interaction timing +- **Keyboard Timing** - Keystroke dynamics and typing patterns +- **Scroll Behavior** - Scrolling velocity and acceleration patterns +- **Touch Gestures** - Multi-touch capabilities and gesture recognition +- **Device Motion** - Accelerometer and gyroscope data analysis + +## Technology Stack + +- **Svelte 4** - Modern reactive framework with excellent performance +- **TypeScript** - Strong typing for better code quality and developer experience +- **Vite** - Fast build tool with instant hot module replacement +- **Modern Web APIs** - Extensive use of cutting-edge browser capabilities + +## Quick Start + +### Prerequisites +- Node.js 18+ +- pnpm (recommended) or npm + +### Installation + +```bash +# Install dependencies +npm install + +# Start development server +pnpm dev +``` + +Open http://localhost:3000 in your browser. + +### Production Build + +```bash +# Build for production +pnpm build + +# Preview production build +pnpm preview +``` + +### Type Checking + +```bash +# Run TypeScript type checking +pnpm check +``` + +## Project Structure + +``` +src/ +├── components/ # Reusable Svelte components +│ ├── ExportButton.svelte +│ ├── FingerprintGrid.svelte +│ ├── FingerprintSection.svelte +│ ├── LanguageSwitch.svelte +│ └── ProgressBar.svelte +├── lib/ # Core fingerprinting logic +│ ├── fingerprint.ts # Main fingerprinting functions +│ ├── webrtc.ts # WebRTC-specific implementations +│ ├── i18n.ts # Internationalization system +│ └── advanced-fingerprint*.js +├── styles/ # Global CSS styles +│ └── global.css +├── types/ # TypeScript type definitions +│ └── fingerprint.ts +└── App.svelte # Main application component +``` + +## Browser Compatibility + +- Chrome/Chromium 80+ +- Firefox 75+ +- Safari 13+ +- Edge 80+ + +**Some features may not be available in all browsers due to API support variations.** + +## Privacy & Ethics + +This tool is designed for educational and research purposes only. It demonstrates the capabilities of browser fingerprinting to: + +- Educate users about digital privacy +- Help developers understand browser security +- Assist researchers in privacy studies +- Enable security professionals to test defenses + +**Important**: Always obtain proper consent before collecting user data in production applications. + +## License + +0BSD + +## Contributing + +Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests to improve the project. diff --git a/index.html b/index.html new file mode 100644 index 0000000..9d54818 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + WhatCanFingerprint + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..5c06c88 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "whatcanfingerprint", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "clean": "rm -rf dist node_modules", + "type-check": "tsc --noEmit" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@tsconfig/svelte": "^5.0.0", + "@types/node": "^20.0.0", + "svelte": "^4.2.7", + "typescript": "^5.0.0", + "vite": "^5.0.3" + }, + "packageManager": "pnpm@8.15.0" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..b233adc --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,754 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^3.0.0 + version: 3.1.2(svelte@4.2.20)(vite@5.4.19) + '@tsconfig/svelte': + specifier: ^5.0.0 + version: 5.0.5 + '@types/node': + specifier: ^20.0.0 + version: 20.19.11 + svelte: + specifier: ^4.2.7 + version: 4.2.20 + typescript: + specifier: ^5.0.0 + version: 5.9.2 + vite: + specifier: ^5.0.3 + version: 5.4.19(@types/node@20.19.11) + +packages: + + /@ampproject/remapping@2.3.0: + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + dev: true + + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@jridgewell/gen-mapping@0.3.13: + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.30 + dev: true + + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.5.5: + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + dev: true + + /@jridgewell/trace-mapping@0.3.30: + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + dev: true + + /@rollup/rollup-android-arm-eabi@4.49.0: + resolution: {integrity: sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64@4.49.0: + resolution: {integrity: sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.49.0: + resolution: {integrity: sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.49.0: + resolution: {integrity: sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-freebsd-arm64@4.49.0: + resolution: {integrity: sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-freebsd-x64@4.49.0: + resolution: {integrity: sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.49.0: + resolution: {integrity: sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.49.0: + resolution: {integrity: sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.49.0: + resolution: {integrity: sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.49.0: + resolution: {integrity: sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-loongarch64-gnu@4.49.0: + resolution: {integrity: sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-ppc64-gnu@4.49.0: + resolution: {integrity: sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.49.0: + resolution: {integrity: sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-musl@4.49.0: + resolution: {integrity: sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.49.0: + resolution: {integrity: sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.49.0: + resolution: {integrity: sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.49.0: + resolution: {integrity: sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.49.0: + resolution: {integrity: sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.49.0: + resolution: {integrity: sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.49.0: + resolution: {integrity: sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.20)(vite@5.4.19): + resolution: {integrity: sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==} + engines: {node: ^18.0.0 || >=20} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^3.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.20)(vite@5.4.19) + debug: 4.4.1 + svelte: 4.2.20 + vite: 5.4.19(@types/node@20.19.11) + transitivePeerDependencies: + - supports-color + dev: true + + /@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.19): + resolution: {integrity: sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==} + engines: {node: ^18.0.0 || >=20} + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.20)(vite@5.4.19) + debug: 4.4.1 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.18 + svelte: 4.2.20 + svelte-hmr: 0.16.0(svelte@4.2.20) + vite: 5.4.19(@types/node@20.19.11) + vitefu: 0.2.5(vite@5.4.19) + transitivePeerDependencies: + - supports-color + dev: true + + /@tsconfig/svelte@5.0.5: + resolution: {integrity: sha512-48fAnUjKye38FvMiNOj0J9I/4XlQQiZlpe9xaNPfe8vy2Y1hFBt8g1yqf2EGjVvHavo4jf2lC+TQyENCr4BJBQ==} + dev: true + + /@types/estree@1.0.8: + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + dev: true + + /@types/node@20.19.11: + resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==} + dependencies: + undici-types: 6.21.0 + dev: true + + /acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + dev: true + + /axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + dev: true + + /code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@types/estree': 1.0.8 + acorn: 8.15.0 + estree-walker: 3.0.3 + periscopic: 3.1.0 + dev: true + + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 + dev: true + + /debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: true + + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: true + + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + dev: true + + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.8 + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + dependencies: + '@types/estree': 1.0.8 + dev: true + + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + + /locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + dev: true + + /magic-string@0.30.18: + resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + dev: true + + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: true + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: true + + /nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + dependencies: + '@types/estree': 1.0.8 + estree-walker: 3.0.3 + is-reference: 3.0.3 + dev: true + + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dev: true + + /postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + dev: true + + /rollup@4.49.0: + resolution: {integrity: sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.49.0 + '@rollup/rollup-android-arm64': 4.49.0 + '@rollup/rollup-darwin-arm64': 4.49.0 + '@rollup/rollup-darwin-x64': 4.49.0 + '@rollup/rollup-freebsd-arm64': 4.49.0 + '@rollup/rollup-freebsd-x64': 4.49.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.49.0 + '@rollup/rollup-linux-arm-musleabihf': 4.49.0 + '@rollup/rollup-linux-arm64-gnu': 4.49.0 + '@rollup/rollup-linux-arm64-musl': 4.49.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.49.0 + '@rollup/rollup-linux-ppc64-gnu': 4.49.0 + '@rollup/rollup-linux-riscv64-gnu': 4.49.0 + '@rollup/rollup-linux-riscv64-musl': 4.49.0 + '@rollup/rollup-linux-s390x-gnu': 4.49.0 + '@rollup/rollup-linux-x64-gnu': 4.49.0 + '@rollup/rollup-linux-x64-musl': 4.49.0 + '@rollup/rollup-win32-arm64-msvc': 4.49.0 + '@rollup/rollup-win32-ia32-msvc': 4.49.0 + '@rollup/rollup-win32-x64-msvc': 4.49.0 + fsevents: 2.3.3 + dev: true + + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + dev: true + + /svelte-hmr@0.16.0(svelte@4.2.20): + resolution: {integrity: sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==} + engines: {node: ^12.20 || ^14.13.1 || >= 16} + peerDependencies: + svelte: ^3.19.0 || ^4.0.0 + dependencies: + svelte: 4.2.20 + dev: true + + /svelte@4.2.20: + resolution: {integrity: sha512-eeEgGc2DtiUil5ANdtd8vPwt9AgaMdnuUFnPft9F5oMvU/FHu5IHFic+p1dR/UOB7XU2mX2yHW+NcTch4DCh5Q==} + engines: {node: '>=16'} + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.30 + '@types/estree': 1.0.8 + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + code-red: 1.0.4 + css-tree: 2.3.1 + estree-walker: 3.0.3 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.18 + periscopic: 3.1.0 + dev: true + + /typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + dev: true + + /vite@5.4.19(@types/node@20.19.11): + resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.19.11 + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.49.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vitefu@0.2.5(vite@5.4.19): + resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + vite: 5.4.19(@types/node@20.19.11) + dev: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..00a559f --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'src/**' diff --git a/src/App.svelte b/src/App.svelte new file mode 100644 index 0000000..245034f --- /dev/null +++ b/src/App.svelte @@ -0,0 +1,56 @@ + + + + +{#key refreshKey} +
+

{t('title')}

+

{t('subtitle')}

+ + {#if loading} +
+ +
+ {/if} + + {#if !loading || Object.keys(fingerprint).length > 0} + + + {/if} +
+{/key} diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..55963ff --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,4 @@ +declare module '*.svelte' { + import type { SvelteComponentTyped } from 'svelte' + export default class extends SvelteComponentTyped {} +} diff --git a/src/components/ExportButton.svelte b/src/components/ExportButton.svelte new file mode 100644 index 0000000..c960388 --- /dev/null +++ b/src/components/ExportButton.svelte @@ -0,0 +1,75 @@ + + +{#key refreshKey} +
+ + +
+{/key} + + diff --git a/src/components/FingerprintGrid.svelte b/src/components/FingerprintGrid.svelte new file mode 100644 index 0000000..4b67650 --- /dev/null +++ b/src/components/FingerprintGrid.svelte @@ -0,0 +1,62 @@ + + +{#key refreshKey} +
+ {#each sections as section} + {#if fingerprint[section.key]} + + {/if} + {/each} +
+{/key} diff --git a/src/components/FingerprintSection.svelte b/src/components/FingerprintSection.svelte new file mode 100644 index 0000000..6c8c519 --- /dev/null +++ b/src/components/FingerprintSection.svelte @@ -0,0 +1,83 @@ + + +
+

{title}

+ + {#if showCanvas} + + {/if} + + {#if showWebGL} + + {/if} + + {#if showAudio} + + {/if} + +
{JSON.stringify(data, null, 2)}
+
+ + diff --git a/src/components/LanguageSwitch.svelte b/src/components/LanguageSwitch.svelte new file mode 100644 index 0000000..a1315c1 --- /dev/null +++ b/src/components/LanguageSwitch.svelte @@ -0,0 +1,40 @@ + + + + + diff --git a/src/components/ProgressBar.svelte b/src/components/ProgressBar.svelte new file mode 100644 index 0000000..3704eb8 --- /dev/null +++ b/src/components/ProgressBar.svelte @@ -0,0 +1,47 @@ + + +{#key refreshKey} +
+
+ {t('loading')} {Math.round(progress)}% ({loaded}/{total}) +
+
+
+
+
+{/key} + + diff --git a/src/lib/advanced-fingerprint.js b/src/lib/advanced-fingerprint.js new file mode 100644 index 0000000..f79474a --- /dev/null +++ b/src/lib/advanced-fingerprint.js @@ -0,0 +1,186 @@ +export function getBehavioralFingerprint() { + return { + mouseMovement: getMouseMovementPattern(), + keyboardTiming: getKeyboardTiming(), + scrollBehavior: getScrollBehavior(), + clickPattern: getClickPattern() + } +} + +function getMouseMovementPattern() { + let movements = [] + let startTime = Date.now() + + const trackMouse = (e) => { + if (Date.now() - startTime < 5000) { + movements.push({ + x: e.clientX, + y: e.clientY, + timestamp: Date.now() - startTime + }) + } else { + document.removeEventListener('mousemove', trackMouse) + } + } + + document.addEventListener('mousemove', trackMouse) + + return { + trackDuration: 5000, + sampleRate: 'continuous' + } +} + +function getKeyboardTiming() { + let keyPresses = [] + let startTime = Date.now() + + const trackKey = (e) => { + if (Date.now() - startTime < 10000) { + keyPresses.push({ + key: e.key, + timestamp: Date.now() - startTime, + keyCode: e.keyCode + }) + } else { + document.removeEventListener('keydown', trackKey) + } + } + + document.addEventListener('keydown', trackKey) + + return { + trackDuration: 10000, + sampleRate: 'onKeyPress' + } +} + +function getScrollBehavior() { + let scrolls = [] + let startTime = Date.now() + + const trackScroll = (e) => { + if (Date.now() - startTime < 15000) { + scrolls.push({ + deltaX: e.deltaX, + deltaY: e.deltaY, + timestamp: Date.now() - startTime + }) + } else { + window.removeEventListener('scroll', trackScroll) + } + } + + window.addEventListener('scroll', trackScroll) + + return { + trackDuration: 15000, + sampleRate: 'onScroll' + } +} + +function getClickPattern() { + let clicks = [] + let startTime = Date.now() + + const trackClick = (e) => { + if (Date.now() - startTime < 20000) { + clicks.push({ + x: e.clientX, + y: e.clientY, + button: e.button, + timestamp: Date.now() - startTime + }) + } else { + document.removeEventListener('click', trackClick) + } + } + + document.addEventListener('click', trackClick) + + return { + trackDuration: 20000, + sampleRate: 'onClick' + } +} + +export function getDeviceOrientation() { + if (!window.DeviceOrientationEvent) return null + + return new Promise((resolve) => { + const handleOrientation = (event) => { + resolve({ + alpha: event.alpha, + beta: event.beta, + gamma: event.gamma, + absolute: event.absolute + }) + window.removeEventListener('deviceorientation', handleOrientation) + } + + window.addEventListener('deviceorientation', handleOrientation) + + setTimeout(() => { + window.removeEventListener('deviceorientation', handleOrientation) + resolve(null) + }, 5000) + }) +} + +export function getDeviceMotion() { + if (!window.DeviceMotionEvent) return null + + return new Promise((resolve) => { + const handleMotion = (event) => { + resolve({ + acceleration: event.acceleration, + accelerationIncludingGravity: event.accelerationIncludingGravity, + rotationRate: event.rotationRate, + interval: event.interval + }) + window.removeEventListener('devicemotion', handleMotion) + } + + window.addEventListener('devicemotion', handleMotion) + + setTimeout(() => { + window.removeEventListener('devicemotion', handleMotion) + resolve(null) + }, 5000) + }) +} + +export function getBatteryStatus() { + if (!navigator.getBattery) return null + + return navigator.getBattery().then(battery => { + const updateBatteryInfo = () => ({ + charging: battery.charging, + chargingTime: battery.chargingTime, + dischargingTime: battery.dischargingTime, + level: battery.level, + timestamp: Date.now() + }) + + battery.addEventListener('chargingchange', updateBatteryInfo) + battery.addEventListener('levelchange', updateBatteryInfo) + battery.addEventListener('chargingtimechange', updateBatteryInfo) + battery.addEventListener('dischargingtimechange', updateBatteryInfo) + + return updateBatteryInfo() + }) +} + +export function getNetworkQuality() { + if (!navigator.connection) return null + + const connection = navigator.connection + + return { + effectiveType: connection.effectiveType, + downlink: connection.downlink, + rtt: connection.rtt, + saveData: connection.saveData, + timestamp: Date.now() + } +} diff --git a/src/lib/advanced-fingerprint2.js b/src/lib/advanced-fingerprint2.js new file mode 100644 index 0000000..20a4a06 --- /dev/null +++ b/src/lib/advanced-fingerprint2.js @@ -0,0 +1,282 @@ +export function getAdvancedTimingAttacks() { + const results = {} + + const testWorkerPerformance = () => { + try { + const start = performance.now() + const worker = new Worker(URL.createObjectURL(new Blob(['postMessage("test")'], {type: 'application/javascript'}))) + worker.onmessage = () => { + results.workerCreationTime = performance.now() - start + worker.terminate() + } + worker.onerror = () => { + results.workerCreationTime = -1 + worker.terminate() + } + } catch (e) { + results.workerCreationTime = -1 + } + } + + const testImageRendering = () => { + const start = performance.now() + const img = new Image() + img.onload = () => { + results.imageLoadTime = performance.now() - start + } + img.onerror = () => { + results.imageLoadTime = -1 + } + img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==' + } + + const testMathPerformance = () => { + const iterations = 100000 + const start = performance.now() + for (let i = 0; i < iterations; i++) { + Math.sin(i) * Math.cos(i) + Math.sqrt(i) + } + results.mathPerformance = performance.now() - start + } + + testWorkerPerformance() + testImageRendering() + testMathPerformance() + + return results +} + +export function getMemoryPressureFingerprint() { + const results = {} + + try { + if (performance.memory) { + results.usedJSHeapSize = performance.memory.usedJSHeapSize + results.totalJSHeapSize = performance.memory.totalJSHeapSize + results.jsHeapSizeLimit = performance.memory.jsHeapSizeLimit + results.memoryRatio = performance.memory.usedJSHeapSize / performance.memory.totalJSHeapSize + } + + const testArray = [] + const startTime = performance.now() + + for (let i = 0; i < 100000; i++) { + testArray.push(new Array(100).fill(Math.random())) + } + + results.arrayCreationTime = performance.now() - startTime + + const gcStart = performance.now() + testArray.length = 0 + results.gcTime = performance.now() - gcStart + + } catch (e) { + results.error = e.message + } + + return results +} + +export function getDetectionEvasion() { + const results = {} + + try { + results.headlessDetection = { + webdriver: !!navigator.webdriver, + languages: navigator.languages.length === 0, + plugins: navigator.plugins.length === 0, + permissions: !navigator.permissions, + webgl: (() => { + const canvas = document.createElement('canvas') + const gl = canvas.getContext('webgl') + if (!gl) return false + const renderer = gl.getParameter(gl.RENDERER) + return renderer.includes('SwiftShader') || renderer.includes('llvmpipe') + })(), + userAgent: /HeadlessChrome|PhantomJS|SlimerJS/.test(navigator.userAgent), + chrome: navigator.userAgent.includes('Chrome') && !window.chrome, + runtimeErrors: (() => { + try { + return !navigator.permissions.query + } catch { + return true + } + })() + } + + results.automationDetection = { + webdriver: !!window.webdriver, + domAutomation: !!window.domAutomation, + callPhantom: !!window.callPhantom, + phantom: !!window.phantom, + nightmare: !!window.__nightmare, + selenium: !!window._Selenium_IDE_Recorder || !!window._selenium || !!window.__selenium_unwrapped, + webdriverIOAsync: !!window.webdriverIO, + webdriverAsync: !!window.webdriverAsync, + fxdriver: !!window.fxdriver_unwrapped, + documentLoadedComplete: document.readyState + } + + } catch (e) { + results.error = e.message + } + + return results +} + +export function getWindowProperties() { + const results = {} + + try { + results.windowSize = { + outer: { width: window.outerWidth, height: window.outerHeight }, + inner: { width: window.innerWidth, height: window.innerHeight }, + screen: { width: screen.width, height: screen.height }, + available: { width: screen.availWidth, height: screen.availHeight } + } + + results.scrollbars = { + visible: window.outerWidth - window.innerWidth > 0 || window.outerHeight - window.innerHeight > 0, + width: window.outerWidth - window.innerWidth, + height: window.outerHeight - window.innerHeight + } + + results.devicePixelRatio = window.devicePixelRatio + + results.chrome = { + runtime: !!window.chrome?.runtime, + loadTimes: !!window.chrome?.loadTimes, + csi: !!window.chrome?.csi, + app: !!window.chrome?.app + } + + } catch (e) { + results.error = e.message + } + + return results +} + +export function getUniqueIdentifiers() { + const results = {} + + try { + results.cookiesEnabled = navigator.cookieEnabled + + results.localStorage = (() => { + try { + const test = 'test' + localStorage.setItem(test, test) + localStorage.removeItem(test) + return true + } catch { + return false + } + })() + + results.sessionStorage = (() => { + try { + const test = 'test' + sessionStorage.setItem(test, test) + sessionStorage.removeItem(test) + return true + } catch { + return false + } + })() + + results.indexedDB = (() => { + try { + return !!window.indexedDB + } catch { + return false + } + })() + + results.mimeTypes = Array.from(navigator.mimeTypes).map(mt => ({ + type: mt.type, + description: mt.description, + suffixes: mt.suffixes + })) + + } catch (e) { + results.error = e.message + } + + return results +} + +export function getPlatformSpecificFeatures() { + const results = {} + + try { + results.mobile = { + touchEvents: 'ontouchstart' in window, + orientation: 'orientation' in window, + maxTouchPoints: navigator.maxTouchPoints || 0, + msMaxTouchPoints: navigator.msMaxTouchPoints || 0 + } + + results.apple = { + webkit: !!window.webkit, + safari: /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent), + standalone: !!window.navigator.standalone, + touchForceChange: 'ontouchforcechange' in window + } + + results.microsoft = { + msSaveBlob: !!window.msSaveBlob, + msLaunchUri: !!navigator.msLaunchUri, + msCredentials: !!window.MSCredentials + } + + results.google = { + chrome: !!window.chrome, + gapi: !!window.gapi, + google: !!window.google + } + + } catch (e) { + results.error = e.message + } + + return results +} + +export function getNetworkFingerprint() { + const results = {} + + try { + if (navigator.connection) { + const conn = navigator.connection + results.connection = { + effectiveType: conn.effectiveType, + downlink: conn.downlink, + rtt: conn.rtt, + saveData: conn.saveData, + type: conn.type + } + } + + results.onlineStatus = navigator.onLine + + results.doNotTrack = navigator.doNotTrack + + results.cookieEnabled = navigator.cookieEnabled + + const xhr = new XMLHttpRequest() + const startTime = performance.now() + xhr.open('GET', window.location.href, true) + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + results.requestTime = performance.now() - startTime + } + } + xhr.send() + + } catch (e) { + results.error = e.message + } + + return results +} diff --git a/src/lib/fingerprint.ts b/src/lib/fingerprint.ts new file mode 100644 index 0000000..07a3b7e --- /dev/null +++ b/src/lib/fingerprint.ts @@ -0,0 +1,637 @@ +import { + getBehavioralFingerprint, + getDeviceOrientation, + getDeviceMotion, + getBatteryStatus, + getNetworkQuality +} from './advanced-fingerprint' +import { + getAdvancedTimingAttacks, + getMemoryPressureFingerprint, + getDetectionEvasion, + getWindowProperties, + getUniqueIdentifiers, + getPlatformSpecificFeatures, + getNetworkFingerprint +} from './advanced-fingerprint2' +import type { + BasicInfo, + ScreenInfo, + TimezoneInfo, + LanguageInfo, + CanvasInfo, + WebGLInfo, + AudioInfo, + FontInfo, + HardwareInfo, + NetworkInfo, + BatteryInfo, + MediaDeviceInfo, + PermissionsInfo, + StorageInfo, + PerformanceInfo, + FingerprintData +} from '../types/fingerprint.js' + +export function getBasicInfo(): BasicInfo { + const nav = navigator as any + return { + userAgent: navigator.userAgent, + platform: navigator.platform, + vendor: navigator.vendor, + cookieEnabled: navigator.cookieEnabled, + doNotTrack: navigator.doNotTrack, + hardwareConcurrency: navigator.hardwareConcurrency, + maxTouchPoints: navigator.maxTouchPoints, + onLine: navigator.onLine, + deviceMemory: nav.deviceMemory, + connection: nav.connection ? { + effectiveType: nav.connection.effectiveType, + downlink: nav.connection.downlink, + rtt: nav.connection.rtt + } : null + } +} + +export function getScreenInfo(): ScreenInfo { + return { + width: screen.width, + height: screen.height, + availWidth: screen.availWidth, + availHeight: screen.availHeight, + colorDepth: screen.colorDepth, + pixelDepth: screen.pixelDepth, + orientation: screen.orientation ? { + type: screen.orientation.type, + angle: screen.orientation.angle + } : null + } +} + +export function getTimezoneInfo(): TimezoneInfo { + return { + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + timezoneOffset: new Date().getTimezoneOffset(), + dateTimeFormat: Intl.DateTimeFormat().resolvedOptions() + } +} + +export function getLanguageInfo(): LanguageInfo { + return { + language: navigator.language, + languages: navigator.languages, + dateTimeFormat: Intl.DateTimeFormat().resolvedOptions(), + numberFormat: Intl.NumberFormat().resolvedOptions() + } +} + +export function getCanvasFingerprint(): CanvasInfo { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + + if (!ctx) { + return { + width: 0, + height: 0, + text: 'Canvas not supported', + hash: '', + imageData: '', + pixelRatio: 1 + } + } + + canvas.width = 400 + canvas.height = 200 + + ctx.textBaseline = 'top' + ctx.font = '14px Arial' + ctx.fillStyle = '#f60' + ctx.fillRect(125, 1, 62, 20) + + ctx.fillStyle = '#069' + ctx.fillText('BrowserLeaks,com 1.0', 2, 15) + + ctx.fillStyle = 'rgba(102, 204, 0, 0.7)' + ctx.fillText('BrowserLeaks,com 1.0', 4, 45) + + ctx.globalCompositeOperation = 'multiply' + ctx.fillStyle = 'rgb(255,0,255)' + ctx.beginPath() + ctx.arc(50, 50, 50, 0, Math.PI * 2, true) + ctx.closePath() + ctx.fill() + + ctx.fillStyle = 'rgb(0,255,255)' + ctx.beginPath() + ctx.arc(100, 50, 50, 0, Math.PI * 2, true) + ctx.closePath() + ctx.fill() + + ctx.fillStyle = 'rgb(255,255,0)' + ctx.beginPath() + ctx.arc(75, 100, 50, 0, Math.PI * 2, true) + ctx.closePath() + ctx.fill() + + const imageData = canvas.toDataURL() + const hash = btoa(imageData).slice(0, 32) + + return { + width: canvas.width, + height: canvas.height, + text: 'Advanced canvas fingerprint', + hash: hash, + imageData: imageData.slice(0, 100) + '...', + pixelRatio: window.devicePixelRatio || 1 + } +} + +export function getWebGLFingerprint(): WebGLInfo | null { + const canvas = document.createElement('canvas') + const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl') as WebGLRenderingContext | null + + if (!gl) return null + + const debugInfo = gl.getExtension('WEBGL_debug_renderer_info') + + const extensions = gl.getSupportedExtensions() || [] + const aliasedLineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE) + const aliasedPointSizeRange = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE) + const maxAnisotropy = (() => { + const ext = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('MOZ_EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') + return ext ? gl.getParameter((ext as any).MAX_TEXTURE_MAX_ANISOTROPY_EXT) : null + })() + + return { + vendor: debugInfo ? gl.getParameter((debugInfo as any).UNMASKED_VENDOR_WEBGL) : 'Unknown', + renderer: debugInfo ? gl.getParameter((debugInfo as any).UNMASKED_RENDERER_WEBGL) : 'Unknown', + version: gl.getParameter(gl.VERSION), + shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION), + maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE), + maxViewportDims: gl.getParameter(gl.MAX_VIEWPORT_DIMS), + maxRenderBufferSize: gl.getParameter(gl.MAX_RENDERBUFFER_SIZE), + maxCombinedTextureImageUnits: gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS), + maxCubeMapTextureSize: gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE), + maxFragmentUniformVectors: gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS), + maxTextureImageUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), + maxVaryingVectors: gl.getParameter(gl.MAX_VARYING_VECTORS), + maxVertexAttribs: gl.getParameter(gl.MAX_VERTEX_ATTRIBS), + maxVertexTextureImageUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS), + maxVertexUniformVectors: gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS), + aliasedLineWidthRange, + aliasedPointSizeRange, + maxAnisotropy, + extensions, + extensionsCount: extensions.length + } +} + +export function getAudioFingerprint(): AudioInfo | { error: string } { + try { + const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)() + return { + sampleRate: audioContext.sampleRate, + maxChannelCount: audioContext.destination.maxChannelCount, + state: audioContext.state + } + } catch (e) { + return { error: (e as Error).message } + } +} + +export function getFontsList(): Promise { + return new Promise((resolve) => { + requestAnimationFrame(() => { + const baseFonts = ['monospace', 'sans-serif', 'serif'] + const testString = 'mmmmmmmmmmlli' + const testSize = '72px' + const h = document.getElementsByTagName('body')[0] + + const s = document.createElement('span') + s.style.fontSize = testSize + s.style.position = 'absolute' + s.style.left = '-9999px' + s.style.top = '-9999px' + s.style.visibility = 'hidden' + s.innerHTML = testString + h.appendChild(s) + + const defaultWidth: Record = {} + const defaultHeight: Record = {} + + for (let baseFont of baseFonts) { + s.style.fontFamily = baseFont + defaultWidth[baseFont] = s.offsetWidth + defaultHeight[baseFont] = s.offsetHeight + } + + const fonts = [ + 'Arial', 'Verdana', 'Helvetica', 'Times New Roman', 'Courier New', + 'Georgia', 'Palatino', 'Garamond', 'Bookman', 'Comic Sans MS', + 'Trebuchet MS', 'Arial Black', 'Impact', 'Lucida Console', + 'Tahoma', 'Geneva', 'Lucida Sans Unicode', 'Franklin Gothic Medium', + 'Arial Narrow', 'Brush Script MT', 'Lucida Handwriting' + ] + + const detected: string[] = [] + + for (let font of fonts) { + let isDetected = false + for (let baseFont of baseFonts) { + s.style.fontFamily = `${font},${baseFont}` + if (s.offsetWidth !== defaultWidth[baseFont] || s.offsetHeight !== defaultHeight[baseFont]) { + isDetected = true + break + } + } + if (isDetected) { + detected.push(font) + } + } + + h.removeChild(s) + resolve(detected) + }) + }) +} + +export function getPluginsInfo(): FontInfo[] { + const plugins: FontInfo[] = [] + for (let i = 0; i < navigator.plugins.length; i++) { + const plugin = navigator.plugins[i] + plugins.push({ + name: plugin.name, + description: plugin.description, + filename: plugin.filename + }) + } + return plugins +} + +export function getHardwareInfo(): HardwareInfo { + const nav = navigator as any + return { + cores: navigator.hardwareConcurrency, + memory: nav.deviceMemory, + touchPoints: navigator.maxTouchPoints, + pointer: nav.pointerEnabled, + msPointer: nav.msPointerEnabled + } +} + +export function getNetworkInfo(): NetworkInfo | null { + if (!(navigator as any).connection) return null + + const connection = (navigator as any).connection + return { + effectiveType: connection.effectiveType, + downlink: connection.downlink, + rtt: connection.rtt, + saveData: connection.saveData + } +} + +export function getBatteryInfo(): Promise { + if (!(navigator as any).getBattery) return Promise.resolve(null) + + return (navigator as any).getBattery().then((battery: any) => ({ + charging: battery.charging, + chargingTime: battery.chargingTime, + dischargingTime: battery.dischargingTime, + level: battery.level + })) +} + +export function getMediaDevices(): Promise { + if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) return Promise.resolve(null) + + return navigator.mediaDevices.enumerateDevices().then(devices => { + return devices.map(device => ({ + kind: device.kind, + deviceId: device.deviceId, + label: device.label + })) + }) +} + +export function getPermissionsInfo(): PermissionsInfo { + const permissions = ['geolocation', 'notifications', 'push', 'persistent-storage'] + const results: PermissionsInfo = {} + + if ((navigator as any).permissions && (navigator as any).permissions.query) { + permissions.forEach(permission => { + results[permission] = 'querying...' + }) + } else { + permissions.forEach(permission => { + results[permission] = 'not supported' + }) + } + + return results +} + +export function getStorageInfo(): StorageInfo { + return { + localStorage: !!window.localStorage, + sessionStorage: !!window.sessionStorage, + indexedDB: !!window.indexedDB, + webSQL: !!(window as any).openDatabase + } +} + +export function getPerformanceInfo(): PerformanceInfo | null { + if (!window.performance) return null + + const timing = (performance as any).timing + return { + navigationStart: timing.navigationStart, + loadEventEnd: timing.loadEventEnd, + domContentLoadedEventEnd: timing.domContentLoadedEventEnd, + responseEnd: timing.responseEnd, + requestStart: timing.requestStart, + domainLookupEnd: timing.domainLookupEnd, + domainLookupStart: timing.domainLookupStart, + connectEnd: timing.connectEnd, + connectStart: timing.connectStart + } +} + +export function getCSSFeatures() { + const features = [ + 'grid', 'flexbox', 'custom-properties', 'backdrop-filter', 'clip-path', + 'mask', 'scroll-snap', 'transforms', 'transition', 'animation', + 'filter', 'object-fit', 'aspect-ratio', 'color-gamut' + ] + + const supported: Record = {} + features.forEach(feature => { + supported[feature] = CSS.supports ? CSS.supports(feature, 'auto') || CSS.supports(`${feature}: auto`) : false + }) + + return supported +} + +export function getDOMAPIs() { + return { + geolocation: !!navigator.geolocation, + pushManager: !!(navigator as any).serviceWorker && 'PushManager' in window, + notification: 'Notification' in window, + serviceWorker: 'serviceWorker' in navigator, + webWorker: 'Worker' in window, + sharedWorker: 'SharedWorker' in window, + broadcastChannel: 'BroadcastChannel' in window, + clipboard: !!navigator.clipboard, + credentials: 'credentials' in navigator, + bluetooth: 'bluetooth' in navigator, + usb: 'usb' in navigator, + serial: 'serial' in navigator, + hid: 'hid' in navigator, + wakeLock: 'wakeLock' in navigator, + vibrate: 'vibrate' in navigator, + share: 'share' in navigator, + requestIdleCallback: 'requestIdleCallback' in window, + intersectionObserver: 'IntersectionObserver' in window, + mutationObserver: 'MutationObserver' in window, + resizeObserver: 'ResizeObserver' in window, + performanceObserver: 'PerformanceObserver' in window + } +} + +export function getCryptoTiming() { + const start = performance.now() + const data = new Uint8Array(1000) + crypto.getRandomValues(data) + const cryptoTime = performance.now() - start + + const hashStart = performance.now() + crypto.subtle.digest('SHA-256', data).then(() => { + const hashTime = performance.now() - hashStart + return { cryptoTime, hashTime } + }).catch(() => ({ cryptoTime, hashTime: -1 })) + + return { cryptoTime } +} + +import { getWebRTCFingerprint } from './webrtc' + +export function getSpeechSynthesis() { + if (!('speechSynthesis' in window)) return null + + const voices = speechSynthesis.getVoices() + return { + voicesCount: voices.length, + voices: voices.map(voice => ({ + name: voice.name, + lang: voice.lang, + default: voice.default, + localService: voice.localService + })), + defaultVoice: voices.find(v => v.default)?.name || null + } +} + +export function getGamepadAPI() { + if (!('getGamepads' in navigator)) return null + + const gamepads = navigator.getGamepads() + return { + supported: true, + gamepadsConnected: Array.from(gamepads).filter(Boolean).length, + gamepads: Array.from(gamepads).filter(Boolean).map(gamepad => ({ + id: gamepad?.id || 'unknown', + mapping: (gamepad?.mapping || 'standard') as string, + connected: gamepad?.connected || false, + buttonsCount: gamepad?.buttons?.length || 0, + axesCount: gamepad?.axes?.length || 0 + })) + } +} + +export function getSecurityHeaders() { + const headers: any = {} + + try { + headers.csp = document.querySelector('meta[http-equiv="Content-Security-Policy"]')?.getAttribute('content') || null + headers.xFrameOptions = null + headers.referrerPolicy = document.querySelector('meta[name="referrer"]')?.getAttribute('content') || null + headers.permissions = document.querySelector('meta[http-equiv="Permissions-Policy"]')?.getAttribute('content') || null + } catch (e: any) { + headers.error = e.message + } + + return headers +} + +export function getAdvancedAudio() { + try { + const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)() + const analyser = audioContext.createAnalyser() + + return { + sampleRate: audioContext.sampleRate, + maxChannelCount: audioContext.destination.maxChannelCount, + state: audioContext.state, + baseLatency: audioContext.baseLatency || 0, + outputLatency: audioContext.outputLatency || 0, + analyserFFTSize: analyser.fftSize, + analyserFrequencyBinCount: analyser.frequencyBinCount + } + } catch (e: any) { + return { error: e.message } + } +} + +async function getBasicDataGroup() { + return { + basic: getBasicInfo(), + screen: getScreenInfo(), + timezone: getTimezoneInfo(), + language: getLanguageInfo(), + hardware: getHardwareInfo(), + storage: getStorageInfo(), + performance: getPerformanceInfo() + } +} + +async function getRenderingDataGroup() { + return { + canvas: getCanvasFingerprint(), + webgl: getWebGLFingerprint(), + audio: getAudioFingerprint(), + advancedAudio: getAdvancedAudio() + } +} + +async function getSystemDataGroup() { + const [fonts] = await Promise.all([ + getFontsList() + ]) + + return { + fonts, + plugins: getPluginsInfo(), + cssFeatures: getCSSFeatures(), + domAPIs: getDOMAPIs(), + speechSynthesis: getSpeechSynthesis(), + gamepadAPI: getGamepadAPI() + } +} + +async function getNetworkDataGroup() { + const [network, networkQuality, webrtcData] = await Promise.allSettled([ + Promise.resolve(getNetworkInfo()), + Promise.resolve(getNetworkQuality()), + getWebRTCFingerprint() + ]) + + return { + network: network.status === 'fulfilled' ? network.value : null, + networkQuality: networkQuality.status === 'fulfilled' ? networkQuality.value : { error: 'Failed to get network quality' }, + webrtcIPs: webrtcData.status === 'fulfilled' ? webrtcData.value : { error: 'Failed to get WebRTC data' }, + networkFingerprint: getNetworkFingerprint() + } +} + +async function getDeviceDataGroup() { + const [battery, media, batteryStatus, orientation, motion] = await Promise.allSettled([ + getBatteryInfo(), + getMediaDevices(), + getBatteryStatus(), + getDeviceOrientation(), + getDeviceMotion() + ]) + + return { + battery: battery.status === 'fulfilled' ? battery.value : { error: 'Failed to get battery info' }, + media: media.status === 'fulfilled' ? media.value : { error: 'Failed to get media devices' }, + batteryStatus: batteryStatus.status === 'fulfilled' ? batteryStatus.value : { error: 'Failed to get battery status' }, + orientation: orientation.status === 'fulfilled' ? orientation.value : { error: 'Failed to get device orientation' }, + motion: motion.status === 'fulfilled' ? motion.value : { error: 'Failed to get device motion' } + } +} + +async function getSecurityDataGroup() { + return { + permissions: (() => { + try { return getPermissionsInfo() } + catch (e) { return { error: (e as Error).message } } + })(), + securityHeaders: getSecurityHeaders(), + detectionEvasion: getDetectionEvasion(), + uniqueIdentifiers: getUniqueIdentifiers() + } +} + +async function getAdvancedDataGroup() { + return { + behavioral: (() => { + try { return getBehavioralFingerprint() } + catch (e) { return { error: (e as Error).message } } + })(), + cryptoTiming: getCryptoTiming(), + timingAttacks: getAdvancedTimingAttacks(), + memoryPressure: getMemoryPressureFingerprint(), + windowProperties: getWindowProperties(), + platformFeatures: getPlatformSpecificFeatures() + } +} + +export async function collectAllFingerprint( + progressCallback?: (data: Partial, progress: number) => void +): Promise { + const totalGroups = 7 + let completedGroups = 0 + let result: Partial = {} + + const updateProgress = (newData: any) => { + completedGroups++ + result = { ...result, ...newData } + if (progressCallback) { + progressCallback(result, (completedGroups / totalGroups) * 100) + } + } + + const promises = [ + getBasicDataGroup().then(updateProgress), + getRenderingDataGroup().then(updateProgress), + getSystemDataGroup().then(updateProgress), + getNetworkDataGroup().then(updateProgress), + getDeviceDataGroup().then(updateProgress), + getSecurityDataGroup().then(updateProgress), + getAdvancedDataGroup().then(updateProgress) + ] + + await Promise.all(promises) + return result as FingerprintData +} + +export async function collectAllFingerprintFast(): Promise { + const [ + basicData, + renderingData, + systemData, + networkData, + deviceData, + securityData, + advancedData + ] = await Promise.all([ + getBasicDataGroup(), + getRenderingDataGroup(), + getSystemDataGroup(), + getNetworkDataGroup(), + getDeviceDataGroup(), + getSecurityDataGroup(), + getAdvancedDataGroup() + ]) + + return { + ...basicData, + ...renderingData, + ...systemData, + ...networkData, + ...deviceData, + ...securityData, + ...advancedData + } +} diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts new file mode 100644 index 0000000..c80e97a --- /dev/null +++ b/src/lib/i18n.ts @@ -0,0 +1,122 @@ +export type Language = 'ru' | 'en' + +interface Translations { + ru: Record + en: Record +} + +const translations: Translations = { + ru: { + title: 'WhatCanFingerprint', + subtitle: 'Полный анализ возможностей fingerprinting\'а браузера', + loading: 'Загрузка...', + exportJson: 'Экспорт JSON', + copy: 'Копировать', + copied: 'Данные скопированы в буфер обмена', + copyError: 'Ошибка копирования', + + // Секции + 'section.basic': 'Основная информация', + 'section.screen': 'Экран', + 'section.timezone': 'Временная зона', + 'section.language': 'Язык', + 'section.canvas': 'Canvas', + 'section.webgl': 'WebGL', + 'section.audio': 'Аудио', + 'section.fonts': 'Шрифты', + 'section.plugins': 'Плагины', + 'section.hardware': 'Аппаратное обеспечение', + 'section.network': 'Сеть', + 'section.battery': 'Батарея', + 'section.media': 'Медиа устройства', + 'section.permissions': 'Разрешения', + 'section.storage': 'Хранилище', + 'section.performance': 'Производительность', + 'section.behavioral': 'Поведенческий', + 'section.orientation': 'Ориентация устройства', + 'section.motion': 'Движение устройства', + 'section.batteryStatus': 'Статус батареи', + 'section.networkQuality': 'Качество сети', + 'section.cssFeatures': 'CSS Функции', + 'section.domAPIs': 'DOM APIs', + 'section.cryptoTiming': 'Криптография', + 'section.speechSynthesis': 'Синтез речи', + 'section.gamepadAPI': 'Геймпады', + 'section.securityHeaders': 'Заголовки безопасности', + 'section.advancedAudio': 'Продвинутое аудио', + 'section.webrtcIPs': 'WebRTC', + 'section.timingAttacks': 'Атаки по времени', + 'section.memoryPressure': 'Давление памяти', + 'section.detectionEvasion': 'Обход детекции', + 'section.windowProperties': 'Свойства окна', + 'section.uniqueIdentifiers': 'Уникальные идентификаторы', + 'section.platformFeatures': 'Платформенные функции', + 'section.networkFingerprint': 'Сетевой отпечаток' + }, + + en: { + title: 'WhatCanFingerprint', + subtitle: 'Complete browser fingerprinting capabilities analysis', + loading: 'Loading...', + exportJson: 'Export JSON', + copy: 'Copy', + copied: 'Data copied to clipboard', + copyError: 'Copy error', + + // Секции + 'section.basic': 'Basic Information', + 'section.screen': 'Screen', + 'section.timezone': 'Timezone', + 'section.language': 'Language', + 'section.canvas': 'Canvas', + 'section.webgl': 'WebGL', + 'section.audio': 'Audio', + 'section.fonts': 'Fonts', + 'section.plugins': 'Plugins', + 'section.hardware': 'Hardware', + 'section.network': 'Network', + 'section.battery': 'Battery', + 'section.media': 'Media Devices', + 'section.permissions': 'Permissions', + 'section.storage': 'Storage', + 'section.performance': 'Performance', + 'section.behavioral': 'Behavioral', + 'section.orientation': 'Device Orientation', + 'section.motion': 'Device Motion', + 'section.batteryStatus': 'Battery Status', + 'section.networkQuality': 'Network Quality', + 'section.cssFeatures': 'CSS Features', + 'section.domAPIs': 'DOM APIs', + 'section.cryptoTiming': 'Cryptography', + 'section.speechSynthesis': 'Speech Synthesis', + 'section.gamepadAPI': 'Gamepads', + 'section.securityHeaders': 'Security Headers', + 'section.advancedAudio': 'Advanced Audio', + 'section.webrtcIPs': 'WebRTC', + 'section.timingAttacks': 'Timing Attacks', + 'section.memoryPressure': 'Memory Pressure', + 'section.detectionEvasion': 'Detection Evasion', + 'section.windowProperties': 'Window Properties', + 'section.uniqueIdentifiers': 'Unique Identifiers', + 'section.platformFeatures': 'Platform Features', + 'section.networkFingerprint': 'Network Fingerprint' + } +} + +let currentLanguage: Language = 'en' + +export function setLanguage(lang: Language) { + currentLanguage = lang +} + +export function getCurrentLanguage(): Language { + return currentLanguage +} + +export function t(key: string): string { + return translations[currentLanguage][key] || key +} + +export function getSectionTitle(sectionKey: string): string { + return t(`section.${sectionKey}`) +} diff --git a/src/lib/webrtc.ts b/src/lib/webrtc.ts new file mode 100644 index 0000000..16b3960 --- /dev/null +++ b/src/lib/webrtc.ts @@ -0,0 +1,68 @@ +export interface WebRTCInfo { + localIPs: string[] + publicIP?: string + connectionType?: string + error?: string +} + +export async function getWebRTCFingerprint(): Promise { + try { + const localIPs = await getLocalIPs() + return { + localIPs, + connectionType: 'unknown' + } + } catch (error) { + return { + localIPs: [], + error: (error as Error).message + } + } +} + +async function getLocalIPs(): Promise { + return new Promise((resolve) => { + const ips: string[] = [] + + try { + const pc = new RTCPeerConnection({ + iceServers: [] + }) + + pc.createDataChannel('') + + pc.onicecandidate = (event) => { + if (!event.candidate) { + pc.close() + resolve(Array.from(new Set(ips))) + return + } + + const candidate = event.candidate.candidate + const ipMatch = candidate.match(/(\d+\.\d+\.\d+\.\d+)/) + + if (ipMatch && ipMatch[1]) { + const ip = ipMatch[1] + if (!ips.includes(ip) && !ip.startsWith('0.')) { + ips.push(ip) + } + } + } + + pc.createOffer() + .then(offer => pc.setLocalDescription(offer)) + .catch(() => { + pc.close() + resolve([]) + }) + + setTimeout(() => { + pc.close() + resolve(Array.from(new Set(ips))) + }, 1000) + + } catch (e) { + resolve([]) + } + }) +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..59f7db3 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,7 @@ +import App from './App.svelte' + +const app = new App({ + target: document.getElementById('app')! +}) + +export default app diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..4310fed --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,47 @@ +/* Глобальные стили */ +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background-color: #f8f9fa; +} + +/* Утилиты */ +.text-center { + text-align: center; +} + +.loading { + text-align: center; + padding: 40px; + color: #666; +} + +/* Сетка */ +.fingerprint-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 20px; + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* Главный контейнер */ +.main-container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +.main-title { + text-align: center; + color: #333; + margin-bottom: 10px; +} + +.main-subtitle { + text-align: center; + color: #666; + margin-bottom: 30px; +} diff --git a/src/types/fingerprint.ts b/src/types/fingerprint.ts new file mode 100644 index 0000000..c4ad441 --- /dev/null +++ b/src/types/fingerprint.ts @@ -0,0 +1,433 @@ +export interface BasicInfo { + userAgent: string + platform: string + vendor: string + cookieEnabled: boolean + doNotTrack: string | null + hardwareConcurrency: number | undefined + maxTouchPoints: number + onLine: boolean + deviceMemory: number | undefined + connection: ConnectionInfo | null +} + +export interface ConnectionInfo { + effectiveType: string + downlink: number + rtt: number +} + +export interface ScreenInfo { + width: number + height: number + availWidth: number + availHeight: number + colorDepth: number + pixelDepth: number + orientation: OrientationInfo | null +} + +export interface OrientationInfo { + type: string + angle: number +} + +export interface TimezoneInfo { + timezone: string + timezoneOffset: number + dateTimeFormat: Intl.ResolvedDateTimeFormatOptions +} + +export interface LanguageInfo { + language: string + languages: readonly string[] + dateTimeFormat: Intl.ResolvedDateTimeFormatOptions + numberFormat: Intl.ResolvedNumberFormatOptions +} + +export interface CanvasInfo { + width: number + height: number + text: string + hash: string + imageData: string + pixelRatio: number +} + +export interface WebGLInfo { + vendor: string + renderer: string + version: string + shadingLanguageVersion: string + maxTextureSize: number + maxViewportDims: number[] + maxRenderBufferSize: number + maxCombinedTextureImageUnits: number + maxCubeMapTextureSize: number + maxFragmentUniformVectors: number + maxTextureImageUnits: number + maxVaryingVectors: number + maxVertexAttribs: number + maxVertexTextureImageUnits: number + maxVertexUniformVectors: number + aliasedLineWidthRange: number[] + aliasedPointSizeRange: number[] + maxAnisotropy: number | null + extensions: string[] + extensionsCount: number +} + +export interface AudioInfo { + sampleRate: number + maxChannelCount: number + state: string +} + +export interface FontInfo { + name: string + description: string + filename: string +} + +export interface HardwareInfo { + cores: number | undefined + memory: number | undefined + touchPoints: number + pointer: boolean | undefined + msPointer: boolean | undefined +} + +export interface NetworkInfo { + effectiveType: string + downlink: number + rtt: number + saveData: boolean +} + +export interface BatteryInfo { + charging: boolean + chargingTime: number + dischargingTime: number + level: number +} + +export interface MediaDeviceInfo { + kind: string + deviceId: string + label: string +} + +export interface PermissionsInfo { + [key: string]: string +} + +export interface StorageInfo { + localStorage: boolean + sessionStorage: boolean + indexedDB: boolean + webSQL: boolean +} + +export interface PerformanceInfo { + navigationStart: number + loadEventEnd: number + domContentLoadedEventEnd: number + responseEnd: number + requestStart: number + domainLookupEnd: number + domainLookupStart: number + connectEnd: number + connectStart: number +} + +export interface BehavioralInfo { + mouseMovement: { + trackDuration: number + sampleRate: string + } + keyboardTiming: { + trackDuration: number + sampleRate: string + } + scrollBehavior: { + trackDuration: number + sampleRate: string + } + clickPattern: { + trackDuration: number + sampleRate: string + } +} + +export interface DeviceOrientationInfo { + alpha: number | null + beta: number | null + gamma: number | null + absolute: boolean +} + +export interface DeviceMotionInfo { + acceleration: DeviceAcceleration | null + accelerationIncludingGravity: DeviceAcceleration | null + rotationRate: DeviceRotationRate | null + interval: number +} + +export interface DeviceAcceleration { + x: number | null + y: number | null + z: number | null +} + +export interface DeviceRotationRate { + alpha: number | null + beta: number | null + gamma: number | null +} + +export interface BatteryStatusInfo { + charging: boolean + chargingTime: number + dischargingTime: number + level: number + timestamp: number +} + +export interface NetworkQualityInfo { + effectiveType: string + downlink: number + rtt: number + saveData: boolean + timestamp: number +} + +export interface CSSFeaturesInfo { + [key: string]: boolean +} + +export interface DOMAPIsInfo { + geolocation: boolean + pushManager: boolean + notification: boolean + serviceWorker: boolean + webWorker: boolean + sharedWorker: boolean + broadcastChannel: boolean + clipboard: boolean + credentials: boolean + bluetooth: boolean + usb: boolean + serial: boolean + hid: boolean + wakeLock: boolean + vibrate: boolean + share: boolean + requestIdleCallback: boolean + intersectionObserver: boolean + mutationObserver: boolean + resizeObserver: boolean + performanceObserver: boolean +} + +export interface CryptoTimingInfo { + cryptoTime: number + hashTime?: number +} + +export interface SpeechSynthesisInfo { + voicesCount: number + voices: Array<{ + name: string + lang: string + default: boolean + localService: boolean + }> + defaultVoice: string | null +} + +export interface GamepadInfo { + supported: boolean + gamepadsConnected: number + gamepads: Array<{ + id: string + mapping: string + connected: boolean + buttonsCount: number + axesCount: number + }> +} + +export interface SecurityHeadersInfo { + csp: string | null + xFrameOptions: string | null + referrerPolicy: string | null + permissions: string | null + error?: string +} + +export interface AdvancedAudioInfo { + sampleRate: number + maxChannelCount: number + state: string + baseLatency: number + outputLatency: number + analyserFFTSize: number + analyserFrequencyBinCount: number +} + +export interface TimingAttacksInfo { + workerCreationTime?: number + imageLoadTime?: number + mathPerformance: number +} + +export interface MemoryPressureInfo { + usedJSHeapSize?: number + totalJSHeapSize?: number + jsHeapSizeLimit?: number + memoryRatio?: number + arrayCreationTime: number + gcTime: number + error?: string +} + +export interface DetectionEvasionInfo { + headlessDetection: { + webdriver: boolean + languages: boolean + plugins: boolean + permissions: boolean + webgl: boolean + userAgent: boolean + chrome: boolean + runtimeErrors: boolean + } + automationDetection: { + webdriver: boolean + domAutomation: boolean + callPhantom: boolean + phantom: boolean + nightmare: boolean + selenium: boolean + webdriverIOAsync: boolean + webdriverAsync: boolean + fxdriver: boolean + documentLoadedComplete: string + } + error?: string +} + +export interface WindowPropertiesInfo { + windowSize: { + outer: { width: number; height: number } + inner: { width: number; height: number } + screen: { width: number; height: number } + available: { width: number; height: number } + } + scrollbars: { + visible: boolean + width: number + height: number + } + devicePixelRatio: number + chrome: { + runtime: boolean + loadTimes: boolean + csi: boolean + app: boolean + } + error?: string +} + +export interface UniqueIdentifiersInfo { + cookiesEnabled: boolean + localStorage: boolean + sessionStorage: boolean + indexedDB: boolean + mimeTypes: Array<{ + type: string + description: string + suffixes: string + }> + error?: string +} + +export interface PlatformFeaturesInfo { + mobile: { + touchEvents: boolean + orientation: boolean + maxTouchPoints: number + msMaxTouchPoints: number + } + apple: { + webkit: boolean + safari: boolean + standalone: boolean + touchForceChange: boolean + } + microsoft: { + msSaveBlob: boolean + msLaunchUri: boolean + msCredentials: boolean + } + google: { + chrome: boolean + gapi: boolean + google: boolean + } + error?: string +} + +export interface NetworkFingerprintInfo { + connection?: { + effectiveType: string + downlink: number + rtt: number + saveData: boolean + type: string + } + onlineStatus: boolean + doNotTrack: string | null + cookieEnabled: boolean + requestTime?: number + error?: string +} + +export interface FingerprintData { + basic: BasicInfo + screen: ScreenInfo + timezone: TimezoneInfo + language: LanguageInfo + canvas: CanvasInfo + webgl: WebGLInfo | null + audio: AudioInfo | { error: string } + fonts: string[] + plugins: FontInfo[] + hardware: HardwareInfo + network: NetworkInfo | null + battery: BatteryInfo | null | { error: string } + media: MediaDeviceInfo[] | null | { error: string } + permissions: PermissionsInfo + storage: StorageInfo + performance: PerformanceInfo | null + behavioral: BehavioralInfo | { error: string } + orientation: DeviceOrientationInfo | null | { error: string } + motion: DeviceMotionInfo | null | { error: string } + batteryStatus: BatteryStatusInfo | null | { error: string } + networkQuality: NetworkQualityInfo | null | { error: string } + cssFeatures: CSSFeaturesInfo + domAPIs: DOMAPIsInfo + cryptoTiming: CryptoTimingInfo + speechSynthesis: SpeechSynthesisInfo | null + gamepadAPI: GamepadInfo | null + securityHeaders: SecurityHeadersInfo + advancedAudio: AdvancedAudioInfo | { error: string } + webrtcIPs: import('../lib/webrtc').WebRTCInfo | { error: string } + timingAttacks: TimingAttacksInfo + memoryPressure: MemoryPressureInfo + detectionEvasion: DetectionEvasionInfo + windowProperties: WindowPropertiesInfo + uniqueIdentifiers: UniqueIdentifiersInfo + platformFeatures: PlatformFeaturesInfo + networkFingerprint: NetworkFingerprintInfo +} diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..09a1bf7 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' + +export default { + preprocess: vitePreprocess() +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..24b749e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..c7f60c6 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.js"] +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..c35d217 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +export default defineConfig({ + plugins: [svelte()], + server: { + port: 3000 + } +})