first commit
This commit is contained in:
commit
448085c068
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal 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
3
.npmrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
shamefully-hoist=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
|
auto-install-peers=true
|
||||||
2
LICENSE
Normal file
2
LICENSE
Normal 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
140
README.md
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
# WhatCanFingerprint
|
||||||
|
|
||||||
|
[](LICENSE)
|
||||||
|
[](https://www.typescriptlang.org/)
|
||||||
|
[](https://svelte.dev/)
|
||||||
|
[](https://vitejs.dev/)
|
||||||
|
[](https://nodejs.org/)
|
||||||
|
|
||||||
|
[](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
12
index.html
Normal 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
21
package.json
Normal 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
754
pnpm-lock.yaml
generated
Normal 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
2
pnpm-workspace.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
packages:
|
||||||
|
- 'src/**'
|
||||||
56
src/App.svelte
Normal file
56
src/App.svelte
Normal 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
4
src/app.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module '*.svelte' {
|
||||||
|
import type { SvelteComponentTyped } from 'svelte'
|
||||||
|
export default class extends SvelteComponentTyped<any, any, any> {}
|
||||||
|
}
|
||||||
75
src/components/ExportButton.svelte
Normal file
75
src/components/ExportButton.svelte
Normal 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>
|
||||||
62
src/components/FingerprintGrid.svelte
Normal file
62
src/components/FingerprintGrid.svelte
Normal 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}
|
||||||
83
src/components/FingerprintSection.svelte
Normal file
83
src/components/FingerprintSection.svelte
Normal 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>
|
||||||
40
src/components/LanguageSwitch.svelte
Normal file
40
src/components/LanguageSwitch.svelte
Normal 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>
|
||||||
47
src/components/ProgressBar.svelte
Normal file
47
src/components/ProgressBar.svelte
Normal 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>
|
||||||
186
src/lib/advanced-fingerprint.js
Normal file
186
src/lib/advanced-fingerprint.js
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
282
src/lib/advanced-fingerprint2.js
Normal file
282
src/lib/advanced-fingerprint2.js
Normal 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
637
src/lib/fingerprint.ts
Normal 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
122
src/lib/i18n.ts
Normal 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
68
src/lib/webrtc.ts
Normal 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
7
src/main.ts
Normal 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
47
src/styles/global.css
Normal 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
433
src/types/fingerprint.ts
Normal 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
5
svelte.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
preprocess: vitePreprocess()
|
||||||
|
}
|
||||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal 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
10
tsconfig.node.json
Normal 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
9
vite.config.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [svelte()],
|
||||||
|
server: {
|
||||||
|
port: 3000
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user