first commit

This commit is contained in:
Lain Iwakura 2025-08-29 00:19:10 +03:00
commit 448085c068
No known key found for this signature in database
GPG Key ID: C7C18257F2ADC6F8
27 changed files with 3143 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -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

3
.npmrc Normal file
View File

@ -0,0 +1,3 @@
shamefully-hoist=true
strict-peer-dependencies=false
auto-install-peers=true

2
LICENSE Normal file
View File

@ -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.

140
README.md Normal file
View File

@ -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.

12
index.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WhatCanFingerprint</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

21
package.json Normal file
View File

@ -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"
}

754
pnpm-lock.yaml generated Normal file
View File

@ -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

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
packages:
- 'src/**'

56
src/App.svelte Normal file
View File

@ -0,0 +1,56 @@
<script lang="ts">
import { onMount } from 'svelte'
import ExportButton from './components/ExportButton.svelte'
import ProgressBar from './components/ProgressBar.svelte'
import FingerprintGrid from './components/FingerprintGrid.svelte'
import LanguageSwitch from './components/LanguageSwitch.svelte'
import { collectAllFingerprint } from './lib/fingerprint.js'
import { t } from './lib/i18n'
import type { FingerprintData } from './types/fingerprint.js'
import './styles/global.css'
let fingerprint: Partial<FingerprintData> = {}
let loading = true
let progress = 0
let loadedSections = 0
let totalSections = 29
let refreshKey = 0
onMount(async () => {
try {
fingerprint = await collectAllFingerprint((data, progressPercent) => {
fingerprint = { ...data }
progress = progressPercent
loadedSections = Object.keys(data).length
})
} catch (error) {
console.error('Ошибка сбора fingerprint:', error)
} finally {
loading = false
}
})
function onLanguageChanged() {
refreshKey += 1
}
</script>
<LanguageSwitch on:languageChanged={onLanguageChanged} />
{#key refreshKey}
<main class="main-container">
<h1 class="main-title">{t('title')}</h1>
<p class="main-subtitle">{t('subtitle')}</p>
{#if loading}
<div class="loading">
<ProgressBar {progress} loaded={loadedSections} total={totalSections} {refreshKey} />
</div>
{/if}
{#if !loading || Object.keys(fingerprint).length > 0}
<ExportButton {fingerprint} {refreshKey} />
<FingerprintGrid {fingerprint} {refreshKey} />
{/if}
</main>
{/key}

4
src/app.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module '*.svelte' {
import type { SvelteComponentTyped } from 'svelte'
export default class extends SvelteComponentTyped<any, any, any> {}
}

View File

@ -0,0 +1,75 @@
<script>
import { t } from '../lib/i18n'
export let fingerprint
export let refreshKey = 0
function exportData() {
const dataStr = JSON.stringify(fingerprint, null, 2)
const dataBlob = new Blob([dataStr], { type: 'application/json' })
const url = URL.createObjectURL(dataBlob)
const link = document.createElement('a')
link.href = url
link.download = `fingerprint-${new Date().toISOString().slice(0, 19)}.json`
link.click()
URL.revokeObjectURL(url)
}
function copyToClipboard() {
const dataStr = JSON.stringify(fingerprint, null, 2)
navigator.clipboard.writeText(dataStr).then(() => {
alert(t('copied'))
}).catch(() => {
alert(t('copyError'))
})
}
</script>
{#key refreshKey}
<div class="export-buttons">
<button on:click={exportData} class="btn btn-primary">
{t('exportJson')}
</button>
<button on:click={copyToClipboard} class="btn btn-secondary">
{t('copy')}
</button>
</div>
{/key}
<style>
.export-buttons {
display: flex;
gap: 10px;
justify-content: center;
margin: 20px 0;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #545b62;
}
</style>

View File

@ -0,0 +1,62 @@
<script>
import FingerprintSection from './FingerprintSection.svelte'
import { getSectionTitle } from '../lib/i18n'
export let fingerprint = {}
export let refreshKey = 0 // для обновления при смене языка
const sections = [
{ key: 'basic' },
{ key: 'screen' },
{ key: 'timezone' },
{ key: 'language' },
{ key: 'canvas', showCanvas: true },
{ key: 'webgl', showWebGL: true },
{ key: 'audio', showAudio: true },
{ key: 'fonts' },
{ key: 'plugins' },
{ key: 'hardware' },
{ key: 'network' },
{ key: 'battery' },
{ key: 'media' },
{ key: 'permissions' },
{ key: 'storage' },
{ key: 'performance' },
{ key: 'behavioral' },
{ key: 'orientation' },
{ key: 'motion' },
{ key: 'batteryStatus' },
{ key: 'networkQuality' },
{ key: 'cssFeatures' },
{ key: 'domAPIs' },
{ key: 'cryptoTiming' },
{ key: 'speechSynthesis' },
{ key: 'gamepadAPI' },
{ key: 'securityHeaders' },
{ key: 'advancedAudio' },
{ key: 'webrtcIPs' },
{ key: 'timingAttacks' },
{ key: 'memoryPressure' },
{ key: 'detectionEvasion' },
{ key: 'windowProperties' },
{ key: 'uniqueIdentifiers' },
{ key: 'platformFeatures' },
{ key: 'networkFingerprint' }
]
</script>
{#key refreshKey}
<div class="fingerprint-grid">
{#each sections as section}
{#if fingerprint[section.key]}
<FingerprintSection
title={getSectionTitle(section.key)}
data={fingerprint[section.key]}
showCanvas={section.showCanvas || false}
showWebGL={section.showWebGL || false}
showAudio={section.showAudio || false}
/>
{/if}
{/each}
</div>
{/key}

View File

@ -0,0 +1,83 @@
<script>
export let title
export let data
export let showCanvas = false
export let showWebGL = false
export let showAudio = false
let canvas = null
let webgl = null
let audio = null
$: if (showCanvas && canvas) {
const ctx = canvas.getContext('2d')
ctx.textBaseline = 'top'
ctx.font = '14px Arial'
ctx.fillText('Canvas test', 2, 2)
}
$: if (showWebGL && webgl) {
const gl = webgl.getContext('webgl') || webgl.getContext('experimental-webgl')
if (gl) {
gl.clearColor(0.1, 0.2, 0.3, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
}
}
</script>
<div class="section">
<h2>{title}</h2>
{#if showCanvas}
<canvas bind:this={canvas} width="300" height="100" style="border: 1px solid #ccc;"></canvas>
{/if}
{#if showWebGL}
<canvas bind:this={webgl} width="100" height="100"></canvas>
{/if}
{#if showAudio}
<audio bind:this={audio} controls></audio>
{/if}
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
<style>
.section {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
}
.section h2 {
margin-top: 0;
color: #495057;
font-size: 1.2em;
border-bottom: 2px solid #007bff;
padding-bottom: 8px;
}
pre {
background: #fff;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 15px;
overflow-x: auto;
font-size: 0.85em;
line-height: 1.4;
max-height: 300px;
overflow-y: auto;
}
canvas {
display: block;
margin: 10px 0;
}
audio {
width: 100%;
margin: 10px 0;
}
</style>

View File

@ -0,0 +1,40 @@
<script>
import { setLanguage, getCurrentLanguage } from '../lib/i18n'
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
let currentLang = getCurrentLanguage()
function switchLanguage() {
const newLang = currentLang === 'en' ? 'ru' : 'en'
currentLang = newLang
setLanguage(newLang)
dispatch('languageChanged')
}
</script>
<button class="lang-switch" on:click={switchLanguage}>
{currentLang === 'en' ? 'RU' : 'EN'}
</button>
<style>
.lang-switch {
position: fixed;
top: 20px;
right: 20px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
font-weight: 500;
font-size: 14px;
transition: background-color 0.2s;
z-index: 1000;
}
.lang-switch:hover {
background: #0056b3;
}
</style>

View File

@ -0,0 +1,47 @@
<script>
import { t } from '../lib/i18n'
export let progress = 0
export let loaded = 0
export let total = 0
export let refreshKey = 0
</script>
{#key refreshKey}
<div class="progress-container">
<div class="progress-text">
{t('loading')} {Math.round(progress)}% ({loaded}/{total})
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: {progress}%"></div>
</div>
</div>
{/key}
<style>
.progress-container {
max-width: 400px;
margin: 20px auto;
}
.progress-text {
margin-bottom: 10px;
font-weight: 500;
color: #495057;
}
.progress-bar {
width: 100%;
height: 8px;
background-color: #e9ecef;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #007bff;
border-radius: 4px;
transition: width 0.2s ease;
}
</style>

View File

@ -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()
}
}

View File

@ -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
}

637
src/lib/fingerprint.ts Normal file
View File

@ -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 <canvas> 1.0', 2, 15)
ctx.fillStyle = 'rgba(102, 204, 0, 0.7)'
ctx.fillText('BrowserLeaks,com <canvas> 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<string[]> {
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<string, number> = {}
const defaultHeight: Record<string, number> = {}
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<BatteryInfo | null> {
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<MediaDeviceInfo[] | null> {
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<string, boolean> = {}
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<FingerprintData>, progress: number) => void
): Promise<FingerprintData> {
const totalGroups = 7
let completedGroups = 0
let result: Partial<FingerprintData> = {}
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<FingerprintData> {
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
}
}

122
src/lib/i18n.ts Normal file
View File

@ -0,0 +1,122 @@
export type Language = 'ru' | 'en'
interface Translations {
ru: Record<string, string>
en: Record<string, string>
}
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}`)
}

68
src/lib/webrtc.ts Normal file
View File

@ -0,0 +1,68 @@
export interface WebRTCInfo {
localIPs: string[]
publicIP?: string
connectionType?: string
error?: string
}
export async function getWebRTCFingerprint(): Promise<WebRTCInfo> {
try {
const localIPs = await getLocalIPs()
return {
localIPs,
connectionType: 'unknown'
}
} catch (error) {
return {
localIPs: [],
error: (error as Error).message
}
}
}
async function getLocalIPs(): Promise<string[]> {
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([])
}
})
}

7
src/main.ts Normal file
View File

@ -0,0 +1,7 @@
import App from './App.svelte'
const app = new App({
target: document.getElementById('app')!
})
export default app

47
src/styles/global.css Normal file
View File

@ -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;
}

433
src/types/fingerprint.ts Normal file
View File

@ -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
}

5
svelte.config.js Normal file
View File

@ -0,0 +1,5 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
preprocess: vitePreprocess()
}

21
tsconfig.json Normal file
View File

@ -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" }]
}

10
tsconfig.node.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.js"]
}

9
vite.config.js Normal file
View File

@ -0,0 +1,9 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
export default defineConfig({
plugins: [svelte()],
server: {
port: 3000
}
})