Compare commits

...
Sign in to create a new pull request.

52 commits

Author SHA1 Message Date
z0ccc
fdd58eb52b Updated image links 2022-06-05 22:32:29 -04:00
z0ccc
43403bb3a1 Added images 2022-06-05 22:30:35 -04:00
z0ccc
bc5409fba7 reduced size of first table column 2022-06-05 15:24:14 -04:00
z0ccc
aec811d3b5 main window to top window 2022-06-05 15:23:11 -04:00
z0ccc
ea3c7fa141 Increased size of first table column 2022-06-05 15:12:11 -04:00
z0ccc
799036b84a Switched method names 2022-06-05 15:07:27 -04:00
z0ccc
25c17a8175 Switched delayed and initial 2022-06-05 14:02:41 -04:00
z0ccc
2e1593fbc7 Added app version 2022-06-02 22:40:17 -04:00
z0ccc
44baad7bb8 Switched to component css 2022-05-25 00:02:59 -04:00
z0ccc
708a9d7f33 Removed console logs 2022-05-22 19:32:23 -04:00
z0ccc
db2d39637d Issue checking for geolocation 2022-05-22 16:37:15 -04:00
z0ccc
5691f9ed97 Removed unused files 2022-05-22 14:30:40 -04:00
z0ccc
59d9128afe Re-organized website 2022-05-18 00:35:54 -04:00
z0ccc
113f57d1dd Added Header bar 2022-05-15 16:03:12 -04:00
z0ccc
143e646e4f Switching locatejs over for vytal 2022-05-13 13:07:57 -04:00
z0ccc
2db9eabf78 Added more useragents 2022-05-13 01:26:16 -04:00
z0ccc
b9b8523eb0 Add key to profiles map 2022-05-13 01:01:26 -04:00
z0ccc
10cd4dedce Style text inputs 2022-05-12 22:39:37 -04:00
z0ccc
247e4edfe7 Fixed 'none' profile 2022-05-12 20:14:41 -04:00
z0ccc
7ba33fb6c6 Removed unnecessary files from boiler plate 2022-05-12 19:43:52 -04:00
z0ccc
14774f2648 Switched profile list for loop 2022-05-12 18:32:02 -04:00
z0ccc
73ea2ede74 Adding city profiles 2022-05-12 00:31:37 -04:00
z0ccc
b25d62dd61 Added component for profile select 2022-05-10 18:59:57 -04:00
z0ccc
c321921f65 No alarm when interval is 0 of empty 2022-05-10 16:41:53 -04:00
z0ccc
7dc60c2157 Added timer feature to user agent option 2022-05-09 23:56:42 -04:00
z0ccc
431d51fd0c Formatted spacing of popup ui 2022-05-08 22:56:03 -04:00
z0ccc
506fad5c41 Added locale to new profile selection for match ip 2022-05-08 20:42:38 -04:00
z0ccc
89f1faffa0 Added more profiles and changed bottom text 2022-05-07 16:15:57 -04:00
z0ccc
62bac340de Save and load custom values 2022-05-06 13:28:24 -04:00
z0ccc
c350da3cc3 Added profile options 2022-05-06 13:01:20 -04:00
z0ccc
b264e5f1bb Fixed overriding timezone bug 2022-05-05 14:30:45 -04:00
z0ccc
27e0e414f9 Only attach if options enabled 2022-05-02 17:31:39 -04:00
z0ccc
ebf2e47b9c Fixed controlled component error for user agent settings 2022-05-02 01:02:32 -04:00
z0ccc
867b9ef33d Fixed bugs with user agent 2022-05-01 20:52:33 -04:00
z0ccc
6f202aeae5 Correctly saving user agent options in storage 2022-04-28 18:58:53 -04:00
z0ccc
83af9c1c7c Added functionality to randomize options 2022-04-28 13:40:32 -04:00
z0ccc
b17865c13f user agent randomization 2022-04-27 00:18:47 -04:00
z0ccc
bec416ae74 Added useragent masking 2022-04-20 21:44:42 -04:00
z0ccc
2117156138 Fixed controlled component issues 2022-04-16 20:22:09 -04:00
z0ccc
8fc026b348 Added custom locales to debugger 2022-04-16 13:39:09 -04:00
z0ccc
3a76962921 Added locale settings for popup 2022-04-16 13:10:53 -04:00
z0ccc
1c4ee91a1c use custom data for debugger 2022-04-15 23:53:21 -04:00
z0ccc
9f7754641e Fixed issues with reloading match ip's 2022-04-15 17:50:01 -04:00
z0ccc
6e2372b28d Synced up reload button with match ip and geo data 2022-04-15 16:08:37 -04:00
z0ccc
8653004136 Added match ip checkboxes 2022-04-15 14:39:42 -04:00
z0ccc
d2860e202c Adding popup code to reload and display ip 2022-04-15 01:27:03 -04:00
z0ccc
f635a63a1e Added new tab page 2022-04-14 01:42:38 -04:00
z0ccc
5fe9b413e3 Added popup button to refresh ip 2022-04-14 00:50:04 -04:00
z0ccc
62a1fc10d2 Added locale to match ip 2022-04-13 20:57:12 -04:00
z0ccc
1da219b110 Better attachemnt method and using ip data 2022-04-13 19:09:56 -04:00
z0ccc
61d52ca16d Initial setup 2022-04-10 14:17:38 -04:00
z0ccc
ad3a42d040 Extension boiler plant 2022-04-10 13:27:05 -04:00
107 changed files with 49105 additions and 13898 deletions

View file

@ -20,7 +20,10 @@ module.exports = {
'operator-linebreak': 'off',
'no-use-before-define': 'off',
'linebreak-style': 'off',
'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.js'] }],
'react/jsx-filename-extension': [
1,
{ extensions: ['.jsx', '.js', '.ts', 'tsx'] },
],
'jsx-a11y/label-has-associated-control': 'off',
'one-var': 'off',
'one-var-declaration-per-line': 'off',
@ -39,6 +42,9 @@ module.exports = {
'react/react-in-jsx-scope': 'off',
'no-bitwise': 'off',
'react/no-array-index-key': 'off',
'dot-notation': 'off',
'nonblock-statement-body-position': 'off',
'react/button-has-type': 'off',
'no-unused-vars': 'warn',
indent: 'off',
},
};

75
.gitignore vendored
View file

@ -1,67 +1,26 @@
env/
.vscode/
./frontend/App.test.js
# backened #
/backend/secret_settings.py
*.log
*.pot
*.pyc
__pycache__
db.sqlite3
media
# Backup files #
*.bak
# Distribution / packaging
.Python build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# frontend
/frontend/.env
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
/images
App.test.js
# dependencies
/frontend/node_modules
/frontend/.pnp
/frontend/.pnp.js
/node_modules
/.pnp
.pnp.js
# testing
/frontend/coverage
/coverage
# production
/frontend/build
/build
# misc
/frontend/.DS_Store
/frontend/.env.local
/frontend/.env.development.local
/frontend/.env.test.local
/frontend/.env.production.local
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/frontend/npm-debug.log*
/frontend/yarn-debug.log*
/frontend/yarn-error.log*

15
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
]
}

View file

@ -1,77 +1,12 @@
# Vytal
Check it out here: https://vytal.io.
## About
Vytal shows you what traces your browser leaves behind while surfing the web. This scan allows you to understand how easy it is to identify and track your browser even while using a VPN or private mode.
A device fingerprint will be generated from your data in the form of a hash. You can sign your hash by entering and saving a signature. You can clear cookies, change your IP or use private mode and reload the page to see if your signature remains the same.
Vytal contains no ads and signup is not required.
## Data Tampering
The data used to create device fingerprints can be spoofed or tampered with to prevent tracking. There are a variety of methods used to do this including VPNs, browser extensions and built in browser options. Some methods of data tampering can be detected.
If data tampering is detected then a red circle with an x will be displayed next to the data value. If the legitimate value cannot be identified then the data will be discarded and will not be used to generate a fingerprint. Clicking on the table row of the tampered value will bring up a dialog box showing the types of tampering.
## Types of Tampering
### Failed Navigator.prototype
`Navigator.prototype[DataType]` returns a value if the data object was tampered with. Otherwise returns an error.
### Failed undefined properties
`Object.getOwnPropertyDescriptor(navigator, [DataType])` returns an object if the data object was tampered with. Otherwise returns undefined.
### Failed Navigator property value
`Object.getOwnPropertyDescriptor(Navigator.prototype, [DataType]).value` returns an error if the data object was tampered with. Otherwise returns undefined.
### Did not match web worker (\_\_\_)
Value does not match the value found in a [web worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers). The web worker value is in the brackets.
### Location data doesn't match system data
Occurs when the location data from ip address does not match your location data from your system (such as your timezone).
### VPN/proxy has been detected
Your ip address is known to be used by proxies or VPNs.
### Failed Date.prototype.setDate.toString()
`Failed Date.prototype.setDate.toString()` returns 'function setDate() { [native code] }' if the data object was NOT tampered with.
### Failed Screen.prototype
`Screen.prototype[DataType]` returns a value if the data object was tampered with. Otherwise returns an error.
### Avail width is greater than width
Happens when the avail width is greater than the normal width (which is impossible).
### Avail height is greater than height
Happens when the height width is greater than the normal height (which is impossible).
## Dev
This application uses a React frontend and a Django backend that communicates using the Django REST framework.
This application is built with Javascript and React.
Backend Django setup:
Clone this repo and run these commands to start the development server.
```
cd backend
python manage.py runserver
```
Frontend React setup:
```
cd frontend
yarn
yarn run start
```
The website can then be accessed at http://localhost:3000/.

View file

@ -1,16 +0,0 @@
"""
ASGI config for backend project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
application = get_asgi_application()

View file

@ -1,146 +0,0 @@
"""
Django settings for backend project.
Generated by 'django-admin startproject' using Django 3.2.5.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""
from pathlib import Path
from secret_settings import *
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'rest_framework',
'django_filters',
'vytal',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
]
ROOT_URLCONF = 'backend.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'backend.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
],
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day'
}
}
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CORS_ORIGIN_WHITELIST = [
'https://vytal.io',
'https://z0ccc.github.io',
'http://localhost:3000'
]

View file

@ -1,14 +0,0 @@
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from vytal import views
from secret_settings import *
router = routers.DefaultRouter()
router.register(r'fingerprint', views.FingerprintView, 'fingerprint')
urlpatterns = [
path(ADMIN_URL + '/', admin.site.urls),
path('', include(router.urls)),
path('', include('vytal.urls')),
]

View file

@ -1,16 +0,0 @@
"""
WSGI config for backend project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
application = get_wsgi_application()

View file

@ -1,22 +0,0 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

Binary file not shown.

View file

@ -1,11 +0,0 @@
from django.contrib import admin
from .models import Fingerprint
class FingerprintAdmin(admin.ModelAdmin):
list_display = ('name', 'hash')
# Register your models here.
admin.site.register(Fingerprint, FingerprintAdmin)

View file

@ -1,6 +0,0 @@
from django.apps import AppConfig
class VytalConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'vytal'

View file

@ -1,22 +0,0 @@
# Generated by Django 3.2.5 on 2021-07-18 04:11
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Fingerprint',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('hash', models.CharField(max_length=32)),
],
),
]

View file

@ -1,11 +0,0 @@
from django.db import models
# Create your models here.
class Fingerprint(models.Model):
name = models.CharField(max_length=100)
hash = models.CharField(max_length=32)
def _str_(self):
return self.name

View file

@ -1,8 +0,0 @@
from rest_framework import serializers
from .models import Fingerprint
class FingerprintSerializer(serializers.ModelSerializer):
class Meta:
model = Fingerprint
fields = ('id', 'name', 'hash')

View file

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View file

@ -1,6 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path('ip/', views.IPView, name='ip'),
]

View file

@ -1,23 +0,0 @@
from rest_framework import viewsets
from .serializers import FingerprintSerializer
from .models import Fingerprint
from django_filters.rest_framework import DjangoFilterBackend
from django.http import JsonResponse
from ipware import get_client_ip
import urllib.request
import json
from secret_settings import *
class FingerprintView(viewsets.ModelViewSet):
serializer_class = FingerprintSerializer
queryset = Fingerprint.objects.all()
filter_backends = [DjangoFilterBackend]
filterset_fields = ['hash']
def IPView(request):
ip = get_client_ip(request)
with urllib.request.urlopen("https://pro.ip-api.com/json/" + ip[0] + "?key=" + API_KEY) as url:
data = json.loads(url.read().decode())
return JsonResponse(data)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 786 B

View file

@ -1,34 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Vytal shows you what traces your browser leaves behind while surfing the web."
/>
<meta name="author" content="z0ccc" />
<meta property="og:title" content="Vytal" />
<meta property="og:url" content="https://vytal.io" />
<meta property="og:img" content="https://vytal.io/vytal.png" />
<meta
property="og:description"
content="Vytal shows you what traces your browser leaves behind while surfing the web."
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Vytal" />
<meta
name="twitter:description"
content="Vytal shows you what traces your browser leaves behind while surfing the web."
/>
<meta name="twitter:image" content="https://vytal.io/vytal.png" />
<link rel="manifest" href="/manifest.json" />
<title>Vytal Privacy Scan</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View file

@ -1,15 +0,0 @@
{
"short_name": "Vytal",
"name": "Vytal Privacy Scan",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -1,13 +0,0 @@
const data = {
locale: Intl.DateTimeFormat().resolvedOptions().locale,
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
timezoneOffset: new Date().getTimezoneOffset(),
deviceMemory: navigator.deviceMemory,
hardwareConcurrency: navigator.hardwareConcurrency,
platform: navigator.platform,
userAgent: navigator.userAgent,
appVersion: navigator.appVersion,
language: navigator.language,
};
postMessage(data);

View file

@ -1,9 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View file

@ -1,92 +0,0 @@
:root {
--main: #943ec5;
--grey: #9fa6b2;
--text: #4b5563;
--border: #ddd;
--issueBackground: #f8d7da;
--issueText: #721c24;
--link: #943ec5;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 15px;
}
.App {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.background {
position: fixed;
width: 100%;
height: 100%;
background: var(--grey);
background: linear-gradient(
165deg,
rgba(87, 35, 117, 1) 0%,
rgba(148, 62, 197, 1) 55%,
rgba(211, 176, 231, 1) 100%
);
z-index: -1;
}
h1 {
margin: 0 0 12px 0;
font-weight: 600;
font-size: 19px;
}
h2 {
margin: 0 0 4px 0;
font-weight: 600;
font-size: 17px;
}
p {
margin: 12px 0 0 0;
}
b {
font-weight: 600;
}
img {
width: 100%;
border-radius: 6px;
border: 1px solid var(--border);
box-sizing: border-box;
display: block;
margin: 0 0 12px 0;
}
.link {
color: var(--link);
text-decoration: none;
}
.link:hover {
text-decoration: underline;
}
@media screen and (max-width: 500px) {
body {
font-size: 14px;
}
h1 {
font-size: 16px;
}
h2 {
margin: 0 0 3px 0;
font-size: 15px;
}
}

View file

@ -1,13 +0,0 @@
import Github from './Github';
import MainColumn from './MainColumn';
import './App.css';
const App = () => (
<div className="App">
<Github />
<div className="background" />
<MainColumn />
</div>
);
export default App;

View file

@ -1,5 +0,0 @@
const ContentBlock = ({ children }) => (
<div className="contentBlock">{children}</div>
);
export default ContentBlock;

View file

@ -1,47 +0,0 @@
.centerBlockInner {
width: 500px;
}
.centerBlockMobile {
display: none;
}
.contentBlock {
color: var(--text);
background-color: #fff;
border-radius: 6px;
box-sizing: border-box;
padding: 24px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
margin: 0 0 24px 0;
min-width: 500px;
}
@media screen and (max-width: 1200px) {
.centerBlockInner {
display: none;
}
.centerBlockMobile {
display: block;
max-width: 650px;
padding: 0 12px;
}
.contentBlock {
padding: 18px;
margin: 0 0 12px 0;
min-width: 400px;
}
.loadBlock {
margin: 0 12px;
}
}
@media screen and (max-width: 500px) {
.contentBlock {
width: 100%;
min-width: 0;
}
}

View file

@ -1,43 +0,0 @@
import { useState, useEffect } from 'react';
import DataContext from './Context';
import BlocksOne from './BlocksOne';
import BlocksTwo from './BlocksTwo';
// import FontsBlock from './FontsBlock';
import { fetchAPI, getWebWorker } from '../utils/common';
import './Blocks.css';
const Blocks = () => {
const [workerData, setWorkerData] = useState();
const [connectionData, setConnectionData] = useState('');
useEffect(() => {
getWebWorker().onmessage = (event) => {
setWorkerData(event.data);
fetchAPI(setConnectionData);
};
}, []);
return (
<>
{connectionData ? (
<DataContext.Provider value={{ workerData, connectionData }}>
<div className="centerBlockInner">
<BlocksOne />
</div>
<div className="centerBlockInner">
<BlocksTwo />
</div>
<div className="centerBlockMobile">
<BlocksOne />
<BlocksTwo />
</div>
</DataContext.Provider>
) : (
<div className="contentBlock loadBlock">
<center>Loading...</center>
</div>
)}
</>
);
};
export default Blocks;

View file

@ -1,15 +0,0 @@
import UserAgentBlock from './UserAgentBlock';
import IntlBlock from './IntlBlock';
import NavigatorBlock from './NavigatorBlock';
import FingerprintBlock from './FingerprintBlock';
const BlocksOne = () => (
<>
<FingerprintBlock />
<NavigatorBlock />
<UserAgentBlock />
<IntlBlock />
</>
);
export default BlocksOne;

View file

@ -1,15 +0,0 @@
import OtherBlock from './OtherBlock';
import ScreenBlock from './ScreenBlock';
import LocationBlock from './LocationBlock';
import ConnectionBlock from './ConnectionBlock';
const BlocksTwo = () => (
<>
<LocationBlock />
<ConnectionBlock />
<ScreenBlock />
<OtherBlock />
</>
);
export default BlocksTwo;

View file

@ -1,30 +0,0 @@
import { useContext } from 'react';
import DataContext from './Context';
import Block from './Block';
import Table from './Table';
import { getConnection } from '../utils/connection';
const LocationBlock = () => {
const { connectionData } = useContext(DataContext);
return (
<Block>
<h1>Connection</h1>
<Table data={getConnection(connectionData)} />
<p>
<b>Explanation:</b> Your IP address reveals information about your
connection.{' '}
<a
className="link"
target="_blank"
rel="noreferrer"
alt="Read more about ip connection"
href="https://en.wikipedia.org/wiki/IP_address"
>
Read more
</a>
</p>
</Block>
);
};
export default LocationBlock;

View file

@ -1,53 +0,0 @@
.fingerprintTable td:first-child {
width: 80px;
font-weight: 600;
}
.boxWrap {
border: 1px solid var(--border);
border-radius: 6px;
padding: 12px;
}
.hash {
text-align: center;
font-weight: 600;
}
form {
margin: 12px 0 0 0;
}
.saveButton {
border: 1px solid var(--grey);
border-radius: 6px;
padding: 6px;
background-color: transparent;
cursor: pointer;
color: var(--text);
margin: 0 0 0 6px;
-webkit-appearance: none;
}
.saveButton:hover {
background-color: var(--border);
}
input[type='text'] {
border: 1px solid var(--grey);
border-radius: 6px;
padding: 6px;
width: 200px;
outline: none;
-webkit-appearance: none;
}
@media screen and (max-width: 500px) {
.boxWrap {
padding: 8px;
}
input[type='text'] {
width: calc(100% - 70px);
}
}

View file

@ -1,62 +0,0 @@
import './FingerprintBlock.css';
import { useState, useEffect, useContext } from 'react';
import DataContext from './Context';
import Block from './Block';
import Table from './Table';
import {
getSignature,
postSignature,
getHash,
getFingerprint,
} from '../utils/fingerprint';
const FingerprintBlock = () => {
const [signature, setSignature] = useState();
const [load, setload] = useState(false);
const { workerData } = useContext(DataContext);
const hash = getHash(workerData);
useEffect(() => {
getSignature(hash, setSignature, setload);
}, []);
return (
<Block>
<h1>Fingerprint</h1>
{load && (
<>
{signature !== undefined ? (
<div className="fingerprintTable">
<Table data={getFingerprint(signature, hash)} />
</div>
) : (
<div className="boxWrap">
<div className="hash">{hash}</div>
</div>
)}
</>
)}
<p>
<b>Explanation:</b> A device fingerprint will be generated from your
data in the form of a hash. Sign your hash, change your IP or use
private mode and reload to see if your signature remains the same.
</p>
<form onSubmit={(e) => postSignature(hash, e, setSignature)}>
<input
type="text"
id="signature"
name="signature"
placeholder="Enter signature"
/>
<input
type="submit"
className="saveButton"
value="Save"
maxLength="100"
/>
</form>
</Block>
);
};
export default FingerprintBlock;

View file

@ -1,61 +0,0 @@
import { useEffect } from 'react';
import Block from './Block';
import fontList from '../fontList.json';
const FontsBlock = () => {
useEffect(() => {
const families = ['serif', 'sans-serif', 'monospace', 'cursive', 'fantasy'];
const famLen = families.length;
const fontsEl = document.querySelector('.fonts');
const width = [];
const height = [];
const span = document.createElement('span');
span.innerHTML = 'AaBbCcWwLl:/!@的下ㅏㅎ平片';
span.style.fontSize = '100px';
for (let i = 0; i < famLen; i++) {
span.style.fontFamily = families[i];
fontsEl.appendChild(span);
width[i] = span.offsetWidth;
height[i] = span.offsetHeight;
fontsEl.removeChild(span);
}
function detect(font) {
let detected = false;
for (let i = 0; i < famLen; i++) {
span.style.fontFamily = `"${font}" ,${families[i]}`;
fontsEl.appendChild(span);
if (span.offsetWidth !== width[i] || span.offsetHeight !== height[i]) {
detected = true;
}
fontsEl.removeChild(span);
}
return detected;
}
let fontStr = '';
fontList.forEach((item) => {
if (detect(item.font)) {
fontStr += `${item.font}, `;
}
});
fontsEl.textContent = fontStr.slice(0, -2);
}, []);
return (
<Block>
<h1>System Fonts</h1>
<div className="fonts boxWrap" />
<p>
<b>Explanation:</b> The fonts you have installed on your computer are
generally linked to operating systems, language and system age. Any
fonts that you have installed that aren&apos;t common for your system
can make you easily identifiable.
</p>
</Block>
);
};
export default FontsBlock;

View file

@ -1,18 +0,0 @@
.gitHubButton {
position: fixed;
top: 12px;
right: 12px;
}
@media screen and (max-width: 900px) {
.gitHubButton {
position: absolute;
}
}
@media screen and (max-width: 500px) {
.gitHubButton {
top: 8px;
right: 8px;
}
}

View file

@ -1,16 +0,0 @@
import './GitHub.css';
import GitHubButton from 'react-github-btn';
const Github = () => (
<div className="gitHubButton">
<GitHubButton
href="https://github.com/z0ccc/Vytal"
data-color-scheme="no-preference: light; light: light; dark: light;"
aria-label="Star z0ccc/Vytal on GitHub"
>
Star
</GitHubButton>
</div>
);
export default Github;

View file

@ -1,29 +0,0 @@
import { useContext } from 'react';
import DataContext from './Context';
import Block from './Block';
import Table from './Table';
import getIntl from '../utils/intl';
const IntlBlock = () => {
const { workerData } = useContext(DataContext);
return (
<Block>
<h1>Intl</h1>
<Table data={getIntl(workerData)} />
<p>
<b>Explanation:</b> The Intl object exposes info about your computer.{' '}
<a
className="link"
target="_blank"
rel="noreferrer"
alt="Read more about intl"
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl"
>
Read more
</a>
</p>
</Block>
);
};
export default IntlBlock;

View file

@ -1,31 +0,0 @@
import { useContext } from 'react';
import DataContext from './Context';
import Block from './Block';
import Table from './Table';
import { getMap, getLocation } from '../utils/connection';
const LocationBlock = () => {
const { workerData, connectionData } = useContext(DataContext);
return (
<Block>
<h1>Location</h1>
<img src={getMap(connectionData)} alt="Map of current location" />
<Table data={getLocation(connectionData, workerData)} />
<p>
<b>Explanation:</b> Your IP address can be used to determine your
location.{' '}
<a
className="link"
target="_blank"
rel="noreferrer"
alt="Read more about ip location"
href="https://en.wikipedia.org/wiki/IP_address"
>
Read more
</a>
</p>
</Block>
);
};
export default LocationBlock;

View file

@ -1,17 +0,0 @@
.logoWrap {
display: flex;
align-items: center;
justify-content: center;
}
.logo {
margin: 20px 0 16px 0;
width: 250px;
}
@media screen and (max-width: 500px) {
.logo {
width: 160px;
margin: 12px 0 8px 0;
}
}

View file

@ -1,12 +0,0 @@
import './Logo.css';
import { ReactComponent as LogoImg } from '../images/logo.svg';
const Logo = () => (
<div className="logoWrap">
<a href="/" className="logo" alt="Vytal logo">
<LogoImg />
</a>
</div>
);
export default Logo;

View file

@ -1,5 +0,0 @@
.centerBlockOuter {
display: flex;
justify-content: center;
gap: 24px;
}

View file

@ -1,13 +0,0 @@
import './MainColumn.css';
import Logo from './Logo';
import Blocks from './Blocks';
const MainColumn = () => (
<>
<Logo />
<div className="centerBlockOuter">
<Blocks />
</div>
</>
);
export default MainColumn;

View file

@ -1,30 +0,0 @@
import { useContext } from 'react';
import DataContext from './Context';
import Block from './Block';
import Table from './Table';
import getNavigator from '../utils/navigator';
const NavigatorBlock = () => {
const { workerData } = useContext(DataContext);
return (
<Block>
<h1>Navigator</h1>
<Table data={getNavigator(workerData)} />
<p>
<b>Explanation:</b> The Navigator interface exposes info about your
computer.{' '}
<a
className="link"
target="_blank"
rel="noreferrer"
alt="Read more about navigator"
href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator"
>
Read more
</a>
</p>
</Block>
);
};
export default NavigatorBlock;

View file

@ -1,43 +0,0 @@
import { useState, useEffect, useContext } from 'react';
import DataContext from './Context';
import Block from './Block';
import Table from './Table';
import getOther from '../utils/other';
const OtherBlock = () => {
const [adBlock, setAdBlock] = useState();
const [battery, setBattery] = useState();
const { workerData } = useContext(DataContext);
useEffect(() => {
fetch('https://www3.doubleclick.net', {
method: 'HEAD',
mode: 'no-cors',
cache: 'no-store',
})
.then(() => {
setAdBlock(false);
})
.catch(() => {
setAdBlock(true);
});
if ('getBattery' in navigator) {
navigator.getBattery().then((res) => {
setBattery(res);
});
} else {
setBattery('N/A');
}
}, []);
return (
<Block>
<h1>Other</h1>
{battery && adBlock !== undefined && (
<Table data={getOther(battery, adBlock, workerData)} />
)}
</Block>
);
};
export default OtherBlock;

View file

@ -1,24 +0,0 @@
import Block from './Block';
import Table from './Table';
import getScreen from '../utils/screen';
const ScreenBlock = () => (
<Block>
<h1>Screen</h1>
<Table data={getScreen()} />
<p>
<b>Explanation:</b> The Screen interface exposes info about your computer.{' '}
<a
className="link"
target="_blank"
rel="noreferrer"
alt="Read more about screen"
href="https://developer.mozilla.org/en-US/docs/Web/API/Screen"
>
Read more
</a>
</p>
</Block>
);
export default ScreenBlock;

View file

@ -1,14 +0,0 @@
table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.tableWrap {
border: 1px solid var(--border);
border-radius: 6px;
}
tbody:not(:last-child) {
border-bottom: 1px solid var(--border);
}

View file

@ -1,16 +0,0 @@
import './Table.css';
import TableRow from './TableRow';
const Table = ({ data }) => (
<div className="tableWrap">
<table>
{data.map((item) => (
<tbody key={item.key} title={item.code}>
<TableRow item={item} />
</tbody>
))}
</table>
</div>
);
export default Table;

View file

@ -1,73 +0,0 @@
td {
padding: 12px;
word-break: break-all;
}
td:first-child {
width: 150px;
font-weight: 600;
word-break: normal;
}
td:nth-child(3) {
width: 40px;
font-weight: 600;
}
.circleButton {
display: flex;
width: 20px;
}
.issue {
cursor: pointer;
}
.issue:hover {
background-color: var(--issueBackground);
color: var(--issueText);
}
.modalHeader {
display: flex;
justify-content: space-between;
margin: 0 0 6px 0;
height: 20px;
}
.modalTitle {
font-weight: 600;
}
.closeButton {
fill: var(--border);
display: flex;
width: 12px;
cursor: pointer;
margin: 0 0 0 12px;
}
.closeButton:hover {
fill: var(--grey);
}
ul {
padding-left: 20px;
margin: 0px;
}
@media screen and (max-width: 500px) {
td {
padding: 8px;
}
td:first-child {
width: 75px;
}
td:nth-child(3) {
width: 20px;
font-weight: 600;
word-break: normal;
}
}

View file

@ -1,68 +0,0 @@
import './TableRow.css';
import Modal from 'react-modal';
import { useState } from 'react';
import { ReactComponent as XCircle } from '../images/xCircle.svg';
import { ReactComponent as CheckCircle } from '../images/checkCircle.svg';
import { ReactComponent as X } from '../images/x.svg';
const modalStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
padding: '18px',
border: '1px solid var(--border)',
borderRadius: '6px',
},
};
Modal.setAppElement('#root');
const TableRow = ({ item }) => {
const issues = item.issues.filter(Boolean).length !== 0;
const [modalIsOpen, setIsOpen] = useState(false);
const openModal = () => {
if (issues) setIsOpen(true);
};
const closeModal = () => {
setIsOpen(false);
};
return (
<>
<tr className={issues ? 'issue' : ''} onClick={openModal}>
<td>{item.key}</td>
<td>{item.value || 'N/A'}</td>
<td>
{issues ? (
<XCircle className="circleButton" />
) : (
<CheckCircle className="circleButton" />
)}
</td>
</tr>
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
style={modalStyles}
contentLabel="Issues Modal"
>
<div className="modalHeader">
<div className="modalTitle">{item.key} issues</div>
<X className="closeButton" onClick={closeModal} />
</div>
<ul>
{item.issues.filter(Boolean).map((ele, index) => (
<li key={index}>{ele}</li>
))}
</ul>
</Modal>
</>
);
};
export default TableRow;

View file

@ -1,30 +0,0 @@
import { useContext } from 'react';
import DataContext from './Context';
import Block from './Block';
import Table from './Table';
import getUserAgent from '../utils/userAgent';
const UserAgentBlock = () => {
const { workerData } = useContext(DataContext);
return (
<Block>
<h1>User Agent</h1>
<Table data={getUserAgent(workerData.userAgent)} />
<p>
<b>Explanation:</b> Your user agent can be parsed to determine
information about your browser or operating system.{' '}
<a
className="link"
target="_blank"
rel="noreferrer"
alt="Read more about user agent"
href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent"
>
Read more
</a>
</p>
</Block>
);
};
export default UserAgentBlock;

File diff suppressed because one or more lines are too long

View file

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<g fill="#9fa6b2">
<path d="M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zM32 80c0-8.8 7.2-16 16-16h48v64H32V80zm448 352c0 8.8-7.2 16-16 16H48c-8.8 0-16-7.2-16-16V160h448v272zm0-304H128V64h336c8.8 0 16 7.2 16 16v48z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 360 B

View file

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<g fill="#9fa6b2">
<path d="M256 169.92c-28.28.41-52.84 9.62-71.37 28.17-18 18-27.69 41.94-27.28 67.44.78 50.23-3.91 100.5-14 149.39-1.81 8.66 3.78 17.12 12.41 18.91 8.5 1.66 17.09-3.77 18.91-12.44 10.56-51.17 15.5-103.78 14.69-156.35-.25-16.77 6.09-32.5 17.91-44.31 18.66-18.66 42.27-18.8 48.75-18.8 37.12.55 66.41 30.19 66.97 66.06.78 50.37-2.97 100.86-11.12 150.06-1.44 8.72 4.44 16.95 13.16 18.39.91.16 1.78.22 2.66.22 7.69 0 14.47-5.55 15.75-13.39 8.47-51.08 12.34-103.48 11.56-155.79-.87-53-44.81-96.76-99-97.56zm-.09 86.09c-8.84.14-15.87 7.41-15.75 16.25 1.12 73.39-8.22 144.99-27.78 215.07l-1.22 4.33c-1.85 6.63 1.74 20.34 15.41 20.34 7 0 13.41-4.61 15.41-11.66l1.22-4.37c20.41-73.08 30.16-147.74 28.97-224.21-.14-8.84-8.23-15.9-16.26-15.75zM112.66 149.79c-25.19 30.98-38.72 70.11-38.09 110.15.62 39.56-2.62 79.14-9.59 117.61-1.56 8.7 4.19 17.03 12.91 18.61.97.17 1.91.25 2.87.25 7.56 0 14.31-5.42 15.72-13.14 7.34-40.53 10.72-82.18 10.09-123.82-.53-33.01 10.19-63.95 30.91-89.47 5.59-6.86 4.53-16.94-2.31-22.51-6.85-5.55-16.91-4.57-22.51 2.32zm399.22 103.03c-.25-16.5-2.19-33.03-5.75-49.14-1.91-8.61-10.34-14-19.06-12.17-8.62 1.91-14.09 10.44-12.19 19.08 3.09 14 4.78 28.39 5 42.73.12 7.66.16 15.31.09 22.97-.06 8.83 7.03 16.05 15.87 16.12h.12c8.78 0 15.94-7.08 16-15.87.07-7.91.04-15.81-.08-23.72zM252.6.05C182.63-1.29 118.32 24.88 70.32 72.91 24.04 119.22-.87 180.76.16 246.2c.12 7.55.06 15.09-.16 22.62-.25 8.83 6.72 16.2 15.56 16.45H16c8.62 0 15.75-6.87 16-15.55.22-8 .28-16.02.16-24.03-.9-56.69 20.69-110.04 60.78-150.16 41.78-41.84 98.9-64.37 159.15-63.48 74.69 1.09 144.87 38.23 187.75 99.32 5.12 7.23 15.12 8.97 22.28 3.91 7.25-5.08 9-15.05 3.91-22.28C417.28 43.52 337.5 1.3 252.6.05zm1.28 84.93a182.36 182.36 0 0 0-45.19 4.91c-8.59 2.03-13.91 10.66-11.87 19.26 2.03 8.58 10.75 13.87 19.25 11.86 12.12-2.86 25.09-4.25 37.31-4.03 82.78 1.22 151.12 67.56 152.37 147.89.62 39.65-1.34 79.59-5.81 118.7-1 8.78 5.31 16.7 14.09 17.7.62.08 1.22.11 1.84.11 8 0 14.94-6.02 15.87-14.19 4.62-40.47 6.62-81.79 6-122.82-1.49-97.44-83.99-177.92-183.86-179.39z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512">
<g fill="#9fa6b2">
<path d="M320 320c-44.18 0-80 35.82-80 80 0 44.19 35.83 80 80 80 44.19 0 80-35.84 80-80 0-44.18-35.82-80-80-80zm0 128c-26.47 0-48-21.53-48-48s21.53-48 48-48 48 21.53 48 48-21.53 48-48 48zm316.21-290.05C459.22-9.9 180.95-10.06 3.79 157.95c-4.94 4.69-5.08 12.51-.26 17.32l5.69 5.69c4.61 4.61 12.07 4.74 16.8.25 164.99-156.39 423.64-155.76 587.97 0 4.73 4.48 12.19 4.35 16.8-.25l5.69-5.69c4.81-4.81 4.67-12.63-.27-17.32zM526.02 270.31c-117.34-104.48-294.86-104.34-412.04 0-5.05 4.5-5.32 12.31-.65 17.2l5.53 5.79c4.46 4.67 11.82 4.96 16.66.67 105.17-93.38 264-93.21 368.98 0 4.83 4.29 12.19 4.01 16.66-.67l5.53-5.79c4.65-4.89 4.38-12.7-.67-17.2z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 746 B

View file

@ -1,16 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View file

@ -1,142 +0,0 @@
/* eslint-disable */
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch((error) => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then((registration) => {
registration.unregister();
})
.catch((error) => {
console.error(error.message);
});
}
}

View file

@ -1,25 +0,0 @@
// Gets location values
const fetchAPI = (setData) => {
fetch('https://api.vytal.io/ip/')
.then((response) => response.json())
.then((json) => {
setData(json);
});
};
const checkWebWorker = (key, worker) => {
if (`${key}` !== `${worker}`) {
return `Did not match web worker (${worker})`;
}
return null;
};
const getWebWorker = () => {
let w;
if (typeof w === 'undefined') {
w = new Worker('worker.js');
}
return w;
};
export { fetchAPI, checkWebWorker, getWebWorker };

View file

@ -1,88 +0,0 @@
const getMap = (data) =>
`https://maps.googleapis.com/maps/api/staticmap?center=${data.lat},${data.lon}&markers=color:red%7Clabel:%7C${data.lat},${data.lon}&size=500x200&zoom=10&key=AIzaSyB-YN-X8PGBSPd7NOaQu4csVhgJUnF3ZGk`;
const compareTimeZone = (locationTimeZone, workerTimeZone) => {
if (locationTimeZone !== workerTimeZone) {
return "Location data doesn't match system data";
}
return null;
};
const checkProxy = (proxy) => {
if (proxy) {
return 'VPN/proxy has been detected';
}
return null;
};
// Returns object with location data
const getLocation = (data, workerData) => {
const timeZoneIssue = compareTimeZone(data.timezone, workerData.timeZone);
const isProxy = checkProxy(data.proxy);
return [
{
key: 'Country',
value: data.country,
issues: [timeZoneIssue, isProxy],
},
{
key: 'Region',
value: data.regionName,
issues: [timeZoneIssue, isProxy],
},
{
key: 'City',
value: data.city,
issues: [timeZoneIssue, isProxy],
},
{
key: 'Time zone',
value: data.timezone,
issues: [timeZoneIssue, isProxy],
},
{
key: 'Zip code',
value: data.zip,
issues: [timeZoneIssue, isProxy],
},
{
key: 'Latitude',
value: data.lat,
issues: [timeZoneIssue, isProxy],
},
{
key: 'Longitude',
value: data.lon,
issues: [timeZoneIssue, isProxy],
},
];
};
// Returns object with location data
const getConnection = (data) => {
const isProxy = checkProxy(data.proxy);
return [
{
key: 'IP address',
value: data.query,
issues: [isProxy],
},
{
key: 'ISP',
value: data.isp,
issues: [isProxy],
},
{
key: 'Org',
value: data.org,
issues: [isProxy],
},
{
key: 'ASN',
value: data.as,
issues: [isProxy],
},
];
};
export { getMap, getConnection, getLocation };

View file

@ -1,39 +0,0 @@
import axios from 'axios';
import md5 from 'crypto-js/md5';
const getSignature = (hash, setSignature, setload) => {
axios
.get(`https://api.vytal.io/fingerprint/?hash=${hash}`)
.then((response) => {
if (response.data.length !== 0) {
setSignature(response.data[response.data.length - 1].name);
}
setload(true);
});
};
const postSignature = (hash, e, setSignature) => {
e.preventDefault();
axios.post('https://api.vytal.io/fingerprint/', {
name: e.target[0].value,
hash,
});
setSignature(e.target[0].value);
};
const getHash = (data) => md5(JSON.stringify(data)).toString();
const getFingerprint = (signature, hash) => [
{
key: 'Signature',
value: signature,
issues: [],
},
{
key: 'Hash',
value: hash,
issues: [],
},
];
export { getSignature, postSignature, getHash, getFingerprint };

View file

@ -1,26 +0,0 @@
import { checkWebWorker } from './common';
const getLocale = (locale) => ({
key: 'Locale',
code: 'Intl.DateTimeFormat().resolvedOptions().locale',
value: Intl.DateTimeFormat().resolvedOptions().locale,
issues: [
checkWebWorker(Intl.DateTimeFormat().resolvedOptions().locale, locale),
],
});
const getTimezone = (timeZone) => ({
key: 'Timezone',
code: 'Intl.DateTimeFormat().resolvedOptions().timeZone',
value: Intl.DateTimeFormat().resolvedOptions().timeZone,
issues: [
checkWebWorker(Intl.DateTimeFormat().resolvedOptions().timeZone, timeZone),
],
});
const getIntl = (workerData) => [
getLocale(workerData.locale),
getTimezone(workerData.timeZone),
];
export default getIntl;

View file

@ -1,262 +0,0 @@
import { checkWebWorker } from './common';
const getDeviceMemory = (worker) => {
const name = 'deviceMemory';
return {
key: 'Device memory',
code: `navigator.${name}`,
value: navigator[name],
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
checkWebWorker(navigator[name], worker),
],
};
};
const getHardwareConcurrency = (worker) => {
const name = 'hardwareConcurrency';
return {
key: 'Hardware concurrency',
code: `navigator.${name}`,
value: navigator[name],
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
checkWebWorker(navigator[name], worker),
],
};
};
const getMaxTouchPoints = () => {
const name = 'maxTouchPoints';
return {
key: 'Max touch points',
code: `navigator.${name}`,
value: navigator[name],
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
],
};
};
const getPlatform = (worker) => {
const name = 'platform';
return {
key: 'Platform',
code: `navigator.${name}`,
value: navigator[name],
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
checkWebWorker(navigator[name], worker),
],
};
};
const getUserAgent = (worker) => {
const name = 'userAgent';
return {
key: 'User agent',
code: `navigator.${name}`,
value: navigator[name],
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
checkWebWorker(navigator[name], worker),
],
};
};
const getAppVersion = (worker) => {
const name = 'appVersion';
return {
key: 'App version',
code: `navigator.${name}`,
value: navigator[name],
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
checkWebWorker(navigator[name], worker),
],
};
};
const getLanguage = (worker) => {
const name = 'language';
return {
key: 'Language',
code: `navigator.${name}`,
value: navigator[name],
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
checkWebWorker(navigator[name], worker),
],
};
};
const getLanguages = () => {
const name = 'languages';
return {
key: 'Languages',
code: `navigator.${name}`,
value: sortArr(navigator[name]),
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
],
};
};
const getCookieEnabled = () => {
const name = 'cookieEnabled';
return {
key: 'Cookie enabled',
code: `navigator.${name}`,
value: navigator[name] ? 'True' : 'False',
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
],
};
};
const getDoNotTrack = () => {
const name = 'doNotTrack';
return {
key: 'Do not track',
code: `navigator.${name}`,
value: navigator[name] ? 'True' : 'False',
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
],
};
};
const getWebDriver = () => {
const name = 'webdriver';
return {
key: 'Web driver',
code: `navigator.${name}`,
value: navigator[name] ? 'True' : 'False',
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
],
};
};
const getPlugins = () => {
const name = 'plugins';
return {
key: 'Plugins',
code: `navigator.${name}`,
value: sortPlugins(navigator[name]),
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
],
};
};
const getVendor = () => {
const name = 'vendor';
return {
key: 'Vendor',
code: `navigator.${name}`,
value: navigator[name],
issues: [
checkNavigatorProperties(name),
checkNavigatorValue(name),
checkNavigatorPrototype(name),
],
};
};
// sorts array into comma separated list
const sortArr = (arr) => {
const arrLength = arr.length;
let list = '';
for (let i = 0; i < arrLength; i++) {
if (i !== 0) list += ', ';
list += arr[i];
}
return list;
};
// sorts plugins object into comma separated list
const sortPlugins = (data) => {
const { length } = data;
let list = '';
for (let i = 0; i < length; i++) {
if (i !== 0) list += ', ';
list += data[i].name;
}
return list;
};
const checkNavigatorProperties = (name) => {
if (Object.getOwnPropertyDescriptor(navigator, name) !== undefined) {
return 'Failed undefined properties';
}
return null;
};
const checkNavigatorValue = (name) => {
try {
// eslint-disable-next-line no-unused-vars
const { value } = Object.getOwnPropertyDescriptor(
Navigator.prototype,
name
);
} catch (err) {
return 'Failed Navigator property value';
}
return null;
};
const checkNavigatorPrototype = (name) => {
try {
// eslint-disable-next-line no-unused-vars
const check = Navigator.prototype[name];
return 'Failed Navigator.prototype';
} catch (err) {
// eslint-disable-next-line no-unused-vars
const check = '';
}
return null;
};
const getNavigator = (workerData) => [
getDeviceMemory(workerData.deviceMemory),
getHardwareConcurrency(workerData.hardwareConcurrency),
getMaxTouchPoints(),
getPlatform(workerData.platform),
getUserAgent(workerData.userAgent),
getAppVersion(workerData.appVersion),
getLanguage(workerData.language),
getLanguages(),
getCookieEnabled(),
getDoNotTrack(),
getWebDriver(),
getPlugins(),
getVendor(),
];
export default getNavigator;

View file

@ -1,77 +0,0 @@
import { checkWebWorker } from './common';
const detectTor = () => {
const date = new Date();
if (
navigator.plugins.length === 0 &&
date.getTimezoneOffset() === 0 &&
window.outerWidth === window.screen.availWidth &&
window.outerHeight === window.screen.availHeight
) {
return true;
}
return false;
};
const checkDatePrototype = () => {
if (!Date.prototype.setDate.toString().includes('[native code]')) {
return 'Failed Date.prototype.setDate.toString()';
}
return null;
};
// Returns object with location data
const getOther = (battery, adBlock, workerData) => {
let batteryLevel, batteryStatus;
if (battery !== 'N/A') {
batteryLevel = `${Math.round(battery.level * 100)}%`;
batteryStatus = battery.charging ? 'Charging' : 'Not charging';
}
return [
{
key: 'Brave browser',
code: 'navigator.brave',
value: navigator.brave ? 'True' : 'False',
issues: [],
},
{
key: 'Tor browser',
value: detectTor() ? 'True' : 'False',
issues: [],
},
{
key: 'Adblock',
value: adBlock ? 'True' : 'False',
issues: [],
},
{
key: 'Date',
code: 'new Date().toString()',
value: new Date().toString(),
issues: [checkDatePrototype()],
},
{
key: 'Timezone offset',
code: 'new Date().getTimezoneOffset()',
value: new Date().getTimezoneOffset(),
issues: [
checkDatePrototype(),
checkWebWorker(
new Date().getTimezoneOffset(),
workerData.timezoneOffset
),
],
},
{
key: 'Battery level',
value: batteryLevel,
issues: [],
},
{
key: 'Battery status',
value: batteryStatus,
issues: [],
},
];
};
export default getOther;

View file

@ -1,161 +0,0 @@
const getWidth = () => {
const name = 'width';
return {
key: 'Width',
code: `window.screen.${name}`,
value: window.screen[name],
issues: [
checkScreenProperties('width'),
checkScreenValue('width'),
checkScreenPrototype('width'),
checkWidth(),
],
};
};
const getAvailWidth = () => {
const name = 'availWidth';
return {
key: 'Avail width',
code: `window.screen.${name}`,
value: window.screen[name],
issues: [
checkScreenProperties('availWidth'),
checkScreenValue('availWidth'),
checkScreenPrototype('availWidth'),
checkWidth(),
],
};
};
const getOuterWidth = () => {
const name = 'outerWidth';
return {
key: 'Outer width',
code: `window.${name}`,
value: window[name],
issues: [],
};
};
const getHeight = () => {
const name = 'height';
return {
key: 'Height',
code: `window.screen.${name}`,
value: window.screen[name],
issues: [
checkScreenProperties('height'),
checkScreenValue('height'),
checkScreenPrototype('height'),
],
};
};
const getAvailHeight = () => {
const name = 'availHeight';
return {
key: 'Avail height',
code: `window.screen.${name}`,
value: window.screen[name],
issues: [
checkScreenProperties('availHeight'),
checkScreenValue('availHeight'),
checkScreenPrototype('availHeight'),
checkHeight(),
],
};
};
const getOuterHeight = () => {
const name = 'outerHeight';
return {
key: 'Outer height',
code: `window.${name}`,
value: window[name],
issues: [],
};
};
const getPixelDepth = () => {
const name = 'pixelDepth';
return {
key: 'Pixel depth',
code: `window.screen.${name}`,
value: window.screen[name],
issues: [
checkScreenProperties('pixelDepth'),
checkScreenValue('pixelDepth'),
checkScreenPrototype('pixelDepth'),
],
};
};
const getColorDepth = () => {
const name = 'colorDepth';
return {
key: 'Color depth',
code: `window.screen.${name}`,
value: window.screen[name],
issues: [
checkScreenProperties('colorDepth'),
checkScreenValue('colorDepth'),
checkScreenPrototype('colorDepth'),
],
};
};
const checkScreenValue = (name) => {
if (
Object.getOwnPropertyDescriptor(Screen.prototype, name).value !== undefined
) {
return 'Failed descriptor.value undefined';
}
return null;
};
const checkScreenPrototype = (name) => {
try {
// eslint-disable-next-line no-unused-vars
const check = Screen.prototype[name];
return 'Failed Screen.prototype';
} catch (err) {
// eslint-disable-next-line no-unused-vars
const check = '';
}
return null;
};
const checkWidth = () => {
if (window.screen.availWidth > window.screen.width) {
return 'Avail width is greater than width';
}
return null;
};
const checkHeight = () => {
if (window.screen.availHeight > window.screen.height) {
return 'Avail height is greater than height';
}
return null;
};
const checkScreenProperties = (name) => {
if (Object.getOwnPropertyDescriptor(window.screen, name) !== undefined) {
return 'Failed undefined properties';
}
return null;
};
const getScreen = () => [
getWidth(),
getAvailWidth(),
getOuterWidth(),
getHeight(),
getAvailHeight(),
getOuterHeight(),
getPixelDepth(),
getColorDepth(),
];
export default getScreen;

View file

@ -1,49 +0,0 @@
import Bowser from 'bowser';
import { checkWebWorker } from './common';
const getUserAgentData = (key, userAgent, workerAgent) => ({
key,
value: userAgent || 'N/A',
issues: [checkWebWorker(userAgent, workerAgent)],
});
// Returns object with location data
const getUserAgent = (workerAgent) => {
const userAgentParsed = Bowser.parse(navigator.userAgent);
const workerAgentParsed = Bowser.parse(workerAgent);
return [
getUserAgentData(
'Browser',
userAgentParsed.browser.name,
workerAgentParsed.browser.name
),
getUserAgentData(
'Browser version',
userAgentParsed.browser.version,
workerAgentParsed.browser.version
),
getUserAgentData('OS', userAgentParsed.os.name, workerAgentParsed.os.name),
getUserAgentData(
'OS version',
userAgentParsed.os.versionName,
workerAgentParsed.os.versionName
),
getUserAgentData(
'Engine',
userAgentParsed.engine.name,
workerAgentParsed.engine.name
),
getUserAgentData(
'Engine version',
userAgentParsed.engine.version,
workerAgentParsed.engine.version
),
getUserAgentData(
'Platform type',
userAgentParsed.platform.type,
workerAgentParsed.platform.type
),
];
};
export default getUserAgent;

File diff suppressed because it is too large Load diff

35698
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,38 +1,40 @@
{
"homepage": "https://z0ccc.github.io",
"homepage": "https://vytal.io",
"name": "vytal",
"version": "0.1.0",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-pro": "^5.15.3",
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/pro-light-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"axios": "0.21.1",
"bowser": "^2.11.0",
"crypto-js": "^4.0.0",
"emailjs-com": "^3.1.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@emotion/react": "^11.9.0",
"@mdx-js/react": "^2.1.1",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"countries-and-timezones": "^3.2.2",
"country-language": "^0.1.7",
"gh-pages": "^3.2.3",
"html-react-parser": "^1.2.8",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-github-btn": "^1.2.1",
"react-modal": "^3.14.3",
"react-scripts": "4.0.3",
"react-webworker": "^2.1.0",
"tslib": "^2.2.0"
"theme-ui": "^0.14.5",
"typescript": "^4.6.4",
"web-vitals": "^1.0.1"
},
"scripts": {
"predeploy": "yarn run build",
"deploy": "echo 'vytal.io' > ./build/CNAME && gh-pages -d build",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"predeploy": "yarn run build",
"deploy": "echo vytal.io > ./build/CNAME && gh-pages -d build"
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
@ -47,11 +49,11 @@
]
},
"devDependencies": {
"eslint": "^7.28.0",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react": "^7.25.1",
"eslint-plugin-react-hooks": "^4.2.0"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

93
public/frame.html Normal file
View file

@ -0,0 +1,93 @@
<!DOCTYPE html>
<script>
const checkDatePrototype = () => {
if (!Date.prototype.setDate.toString().includes('[native code]')) {
return 'Failed Date.prototype.setDate.toString()';
}
return null;
};
const checkIntlConstructor = () => {
if (!Object.getPrototypeOf(Intl.DateTimeFormat.prototype).constructor.toString().includes('Object')) {
return 'Failed Object.getPrototypeOf(Intl.DateTimeFormat.prototype).constructor.toString()';
}
return null;
};
const checkIntlPrototype = () => {
if (!Intl.DateTimeFormat.prototype.resolvedOptions.toString().includes('[native code]')) {
return 'Failed Intl.DateTimeFormat.prototype.resolvedOptions.toString()';
}
return null;
};
const checkNavigatorProperties = (key) => {
if (Object.getOwnPropertyDescriptor(navigator, key) !== undefined) {
return 'Failed Object.getOwnPropertyDescriptor(navigator, key)';
}
return null;
};
const checkNavigatorValue = (key) => {
if (
Object.getOwnPropertyDescriptor(Navigator.prototype, key).value !==
undefined
) {
return 'Failed object.getOwnPropertyDescriptor(Navigator.prototype, key).value';
}
return null;
};
const checkNavigatorPrototype = (key) => {
try {
// eslint-disable-next-line no-unused-vars
const check = Navigator.prototype[key];
return 'Failed Navigator.prototype[key]';
} catch (err) {
return null;
}
};
const getNavigatorValue = (type) =>
[checkNavigatorProperties(type),
checkNavigatorValue(type),
checkNavigatorPrototype(type)].filter(Boolean);
const data = {
type: 'frameData',
data: {
locale: {
value: Intl.DateTimeFormat().resolvedOptions().locale,
issues: [checkIntlPrototype(), checkIntlConstructor()].filter(Boolean)
},
timeZone: {
value: Intl.DateTimeFormat().resolvedOptions().timeZone,
issues: [checkIntlPrototype(), checkIntlConstructor()].filter(Boolean)
},
timezoneOffset: {
value: new Date().getTimezoneOffset(),
issues: [checkDatePrototype()].filter(Boolean),
},
dateString: {
value: new Date().toString(),
issues: [checkDatePrototype()].filter(Boolean)
},
dateLocale: {
value: new Date().toLocaleString(),
issues: [checkDatePrototype()].filter(Boolean),
},
userAgent: {
value: navigator.userAgent,
issues: getNavigatorValue('userAgent'),
},
appVersion: {
value: navigator.appVersion,
issues: getNavigatorValue('appVersion'),
},
}
};
parent.postMessage(data, '*');
</script>

97
public/index.html Normal file
View file

@ -0,0 +1,97 @@
<!DOCTYPE html>
<script>
const initialDataObj = {
locale: {
value: Intl.DateTimeFormat().resolvedOptions().locale,
issues: [],
},
timeZone: {
value: Intl.DateTimeFormat().resolvedOptions().timeZone,
issues: [],
},
timezoneOffset: {
value: new Date().getTimezoneOffset(),
issues: [],
},
dateString: {
value: new Date().toString(),
issues: [],
},
dateLocale: {
value: new Date().toLocaleString(),
issues: [],
},
userAgent: {
value: navigator.userAgent,
issues: [],
},
appVersion: {
value: navigator.appVersion,
issues: [],
},
};
</script>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#943ec5" />
<meta
name="description"
content="Check if your location is actually hidden."
/>
<meta name="author" content="z0ccc" />
<meta property="og:title" content="Vytal" />
<meta property="og:url" content="https://vytal.io" />
<meta
property="og:image:secure"
content="https://vytal.io/preview.png"
/>
<meta
property="og:img"
content="https://vytal.io/preview.png"
/>
<meta
property="og:description"
content="Check if your location is actually hidden."
/>
<meta
name="twitter:card"
content="https://vytal.io/preview.png"
/>
<meta name="twitter:title" content="Vytal" />
<meta
name="twitter:description"
content="Check if your location is actually hidden."
/>
<meta
name="twitter:image"
content="https://vytal.io/preview.png"
/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo-192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Vytal Extension Scan</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<iframe id="iframe" sandbox="allow-same-origin" style="display: none"></iframe>
</body>
</html>

BIN
public/logo-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
public/logo-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

25
public/manifest.json Normal file
View file

@ -0,0 +1,25 @@
{
"short_name": "Vytal",
"name": "Vytal",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "rgb(87, 35, 117)",
"background_color": "rgb(87, 35, 117)"
}

View file

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

1
public/scan.html Normal file
View file

@ -0,0 +1 @@
<script>window.location.replace("/");</script>

83
public/worker.js Normal file
View file

@ -0,0 +1,83 @@
const checkDatePrototype = () => {
if (!Date.prototype.setDate.toString().includes('[native code]')) {
return 'Failed Date.prototype.setDate.toString()';
}
return null;
};
const checkIntlConstructor = () => {
if (
!Object.getPrototypeOf(Intl.DateTimeFormat.prototype)
.constructor.toString()
.includes('Object')
) {
return 'Failed Object.getPrototypeOf(Intl.DateTimeFormat.prototype).constructor.toString()';
}
return null;
};
const checkIntlPrototype = () => {
if (
!Intl.DateTimeFormat.prototype.resolvedOptions
.toString()
.includes('[native code]')
) {
return 'Failed Intl.DateTimeFormat.prototype.resolvedOptions.toString()';
}
return null;
};
const checkNavigatorProperties = (key) => {
if (Object.getOwnPropertyDescriptor(navigator, key) !== undefined) {
return 'Failed Object.getOwnPropertyDescriptor(navigator, key)';
}
return null;
};
const checkNavigatorPrototype = (key) => {
try {
// eslint-disable-next-line no-unused-vars
const check = Navigator.prototype[key];
return 'Failed Navigator.prototype[key]';
} catch (err) {
return null;
}
};
const getNavigatorValue = (type) =>
[checkNavigatorProperties(type), checkNavigatorPrototype(type)].filter(
Boolean
);
const data = {
locale: {
value: Intl.DateTimeFormat().resolvedOptions().locale,
issues: [checkIntlPrototype(), checkIntlConstructor()].filter(Boolean),
},
timeZone: {
value: Intl.DateTimeFormat().resolvedOptions().timeZone,
issues: [checkIntlPrototype(), checkIntlConstructor()].filter(Boolean),
},
timezoneOffset: {
value: new Date().getTimezoneOffset(),
issues: [checkDatePrototype()].filter(Boolean),
},
dateString: {
value: new Date().toString(),
issues: [checkDatePrototype()].filter(Boolean),
},
dateLocale: {
value: new Date().toLocaleString(),
issues: [checkDatePrototype()].filter(Boolean),
},
userAgent: {
value: navigator.userAgent,
issues: getNavigatorValue('userAgent'),
},
appVersion: {
value: navigator.appVersion,
issues: getNavigatorValue('appVersion'),
},
};
postMessage(data);

92
src/components/App.css Normal file
View file

@ -0,0 +1,92 @@
:root {
--main: #943ec5;
--text: rgb(0, 0, 0, 0.8);
--border: #ddd;
--grey: #c4c4c4;
--lightGrey: #f8f8f8;
--issueBackground: #f8d7da;
--issueText: #721c24;
--fill: #9fa6b2;
--link: #943ec5;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 24px;
font-size: 14px;
}
h1 {
margin: 0 0 10px 0;
font-weight: 600;
font-size: 16px;
word-break: break-all;
}
p {
margin: 10px 0 0 0;
}
b {
font-weight: 600;
}
.tableWrap {
border: 1px solid var(--border);
border-radius: 4px;
}
table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
font-size: inherit;
color: inherit;
line-height: inherit;
}
tr:not(:last-child) {
border-bottom: 1px solid var(--border);
}
td {
padding: 10px;
word-break: break-all;
}
td:first-child {
width: 105px;
font-weight: 600;
word-break: normal;
}
td:nth-child(3) {
width: 40px;
font-weight: 600;
word-break: normal;
}
ul {
padding-left: 20px;
margin: 0px;
}
@media screen and (max-width: 575px) {
body {
font-size: 13px;
}
h1 {
font-size: 15px;
}
td:first-child {
width: 70px;
}
}

25
src/components/App.js Normal file
View file

@ -0,0 +1,25 @@
/** @jsxImportSource theme-ui */
import { ThemeProvider } from 'theme-ui';
import theme from './theme.ts';
import './App.css';
import MainColumn from './MainColumn';
const App = () => (
<ThemeProvider theme={theme}>
<div sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
<div
sx={{
position: 'fixed',
width: '100%',
height: '100%',
background: 'var(--main)',
zIndex: '-1',
}}
/>
<MainColumn />
</div>
</ThemeProvider>
);
export default App;

21
src/components/Block.js Normal file
View file

@ -0,0 +1,21 @@
/** @jsxImportSource theme-ui */
const ContentBlock = ({ children }) => (
<div
sx={{
color: 'var(--text)',
backgroundColor: '#fff',
borderRadius: '4px',
boxSizing: 'border-box',
padding: ['18px', '20px', '20px'],
boxShadow: 'rgba(0, 0, 0, 0.1) 0px 4px 12px',
mb: ['12px', '24px', '24px'],
width: ['100%', 'auto', 'auto'],
minWidth: ['0', '500px', '500px'],
}}
>
{children}
</div>
);
export default ContentBlock;

111
src/components/Blocks.js Normal file
View file

@ -0,0 +1,111 @@
/** @jsxImportSource theme-ui */
import { useState, useEffect } from 'react';
import DataContext from './Context';
import InfoBlock from './InfoBlock';
import DataBlock from './DataBlock';
import GeolocationBlock from './GeolocationBlock';
import windowData from '../utils/data';
const getWebWorker = () => {
let w;
if (typeof w === 'undefined') {
w = new Worker('/worker.js');
}
return w;
};
const Blocks = () => {
const [workerData, setWorkerData] = useState();
const [frameData, setFrameData] = useState();
// eslint-disable-next-line no-undef
const initialData = initialDataObj;
useEffect(() => {
const frame = document.createElement('iframe');
document.body.appendChild(frame);
frame.style.display = 'none';
frame.src = '/frame.html';
const receiveMessage = (event) => {
if (event.data.type === 'frameData') {
setFrameData(event.data.data);
}
};
window.addEventListener('message', receiveMessage, false);
if (window.Worker.length) {
getWebWorker().onmessage = (event) => {
setWorkerData(event.data);
};
} else {
setWorkerData(true);
}
}, []);
return (
<>
<DataContext.Provider
value={{
initialData,
windowData,
frameData,
workerData,
}}
>
<div sx={{ display: ['none', 'none', 'block'], maxWidth: '500px' }}>
<InfoBlock />
<DataBlock
title="Intl.DateTimeFormat().resolvedOptions().timeZone"
type="timeZone"
/>
<DataBlock title="navigator.userAgent" type="userAgent" />
<DataBlock title="navigator.appVersion" type="appVersion" />
</div>
<div sx={{ display: ['none', 'none', 'block'], maxWidth: '500px' }}>
<DataBlock
title="Intl.DateTimeFormat().resolvedOptions().locale"
type="locale"
/>
<DataBlock
title="new Date().getTimezoneOffset()"
type="timezoneOffset"
/>
<DataBlock title="new Date().toLocaleString()" type="dateLocale" />
<DataBlock title="new Date().toString()" type="dateString" />
<GeolocationBlock />
</div>
<div
sx={{
display: ['block', 'block', 'none'],
maxWidth: '500px',
margin: '0 12px',
}}
>
<InfoBlock />
<DataBlock
title="Intl.DateTimeFormat().resolvedOptions().timeZone"
type="timeZone"
/>
<DataBlock
title="Intl.DateTimeFormat().resolvedOptions().locale"
type="locale"
/>
<DataBlock title="new Date().toString()" type="dateString" />
<DataBlock title="new Date().toLocaleString()" type="dateLocale" />
<DataBlock
title="new Date().getTimezoneOffset()"
type="timezoneOffset"
/>
<DataBlock title="navigator.userAgent" type="userAgent" />
<DataBlock title="navigator.appVersion" type="appVersion" />
<GeolocationBlock />
</div>
</DataContext.Provider>
</>
);
};
export default Blocks;

View file

@ -0,0 +1,55 @@
import { useContext } from 'react';
import DataContext from './Context';
import Block from './Block';
import TableRow from './TableRow';
const DataBlock = ({ title, type }) => {
const { initialData, windowData, frameData, workerData } =
useContext(DataContext);
const isWorkerValid = workerData && window.Worker.length;
const getWorkerValue = () => {
if (isWorkerValid) return workerData[type].value;
if (!window.Worker.length) return 'null';
return null;
};
return (
<Block>
<h1>{title}</h1>
<div className="tableWrap">
<table>
<tbody>
<TableRow
title="Top Window"
value={windowData ? windowData[type].value : ''}
issues={windowData ? windowData[type].issues : []}
/>
<TableRow
title="Initial Load"
value={initialData ? initialData[type].value : ''}
issues={initialData ? initialData[type].issues : []}
/>
<TableRow
title="Frame"
value={frameData ? frameData[type].value : ''}
issues={frameData ? frameData[type].issues : []}
/>
<TableRow
title="Web worker"
value={getWorkerValue()}
issues={
isWorkerValid
? workerData[type].issues
: ['Web workers blocked']
}
/>
</tbody>
</table>
</div>
</Block>
);
};
export default DataBlock;

View file

@ -0,0 +1,79 @@
/** @jsxImportSource theme-ui */
import { useState } from 'react';
import Block from './Block';
import getGeolocation from '../utils/geolocation';
import TableRow from './TableRow';
const GeolocationBlock = () => {
const [geolocationData, setGeolocationData] = useState();
const [buttonValue, setButtonValue] = useState('Allow Geolocation API');
return (
<Block>
<h1>HTML Geolocation API</h1>
{geolocationData ? (
<>
{typeof geolocationData === 'string' ? (
<div
sx={{
border: '1px solid var(--border)',
borderRadius: '4px',
padding: '10px',
}}
>
<div sx={{ textAlign: 'center', fontWeight: '600' }}>
{`${geolocationData}`}
</div>
</div>
) : (
<>
<div className="tableWrap">
<table>
<tbody>
{geolocationData.map((item) => (
<TableRow
key={item.key}
title={item.key}
value={item.value}
issues={item.issues}
/>
))}
</tbody>
</table>
</div>
</>
)}
</>
) : (
<input
type="submit"
onClick={() => {
getGeolocation(setGeolocationData);
setButtonValue('Loading...');
}}
sx={{
display: 'block',
backgroundColor: 'var(--main)',
color: '#fff',
borderRadius: '4px',
boxSizing: 'border-box',
textAlign: 'center',
width: '100%',
height: '46px',
border: 'none',
cursor: 'pointer',
fontFamily: 'inherit',
fontSize: 'inherit',
WebkitAppearance: 'none',
':hover': {
opacity: '0.7',
},
}}
value={buttonValue}
/>
)}
</Block>
);
};
export default GeolocationBlock;

View file

@ -0,0 +1,63 @@
/** @jsxImportSource theme-ui */
import { ReactComponent as LogoImg } from '../images/logo.svg';
import HeaderButton from './HeaderButton';
import chromeImage from '../images/chrome.png';
import githubImage from '../images/github.png';
const HeaderBar = () => (
<div
sx={{
display: ['block', 'block', 'flex'],
alignItems: 'center',
justifyContent: ['center', 'center', 'space-between'],
width: ['500px', '500px', '1024px'],
margin: ['12px', '18px', '18px'],
}}
>
<div
sx={{
display: 'flex',
justifyContent: 'center',
mb: ['12px', '18px', '0'],
}}
>
<a
href="."
style={{
display: 'block',
width: '206px',
height: '50px',
}}
alt="Vytal logo"
>
<LogoImg />
</a>
</div>
<div
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: ['36px', '50px', '50px'],
gap: ['12px', '24px', '24px'],
}}
>
<HeaderButton
url="https://chrome.google.com/webstore/detail/vytal/ncbknoohfjmcfneopnfkapmkblaenokb"
image={chromeImage}
text="Download Extension"
alt="Chrome logo"
/>
<HeaderButton
url="https://github.com/z0ccc/Vytal"
image={githubImage}
text="Source Code"
alt="Github logo"
/>
</div>
</div>
);
export default HeaderBar;

View file

@ -0,0 +1,38 @@
/** @jsxImportSource theme-ui */
const HeaderButton = ({ url, image, text, alt }) => (
<a
href={url}
className="headerButton"
target="_blank"
rel="noopener noreferrer"
sx={{
display: 'flex',
alignItems: 'center',
backgroundColor: '#fff',
height: ['36px', '50px', '50px'],
borderRadius: '4px',
px: ['10px', '18px', '18px'],
boxShadow: 'rgb(0 0 0 / 10%) 0px 4px 12px',
fontSize: ['13px', '15px', '15px'],
textDecoration: 'none',
color: '#000',
':hover': {
backgroundColor: 'white90',
},
}}
>
<img
src={image}
alt={alt}
sx={{
marginRight: ['5px', '8px', '8px'],
height: ['22px', '28px', '28px'],
width: ['22px', '28px', '28px'],
}}
/>
{text}
</a>
);
export default HeaderButton;

View file

@ -0,0 +1,13 @@
import Block from './Block';
const InfoBlock = () => (
<Block>
Vytal is a browser extension that utilizes the chrome.debugger API to mock
device data that could otherwise reveal information about you. This website
scans your browser for such data. A red x signifies that the scanner has
detected tampered data. A green check means that no tampering has been
detected.
</Block>
);
export default InfoBlock;

View file

@ -0,0 +1,22 @@
/** @jsxImportSource theme-ui */
import HeaderBar from './HeaderBar';
import Blocks from './Blocks';
const MainColumn = () => (
<div
sx={{
display: 'flex',
justifyContent: 'center',
flexWrap: 'wrap',
width: '100%',
}}
>
<HeaderBar />
<div sx={{ display: 'flex', justifyContent: 'center', gap: '24px' }}>
<Blocks />
</div>
</div>
);
export default MainColumn;

106
src/components/TableRow.js Normal file
View file

@ -0,0 +1,106 @@
/** @jsxImportSource theme-ui */
import Modal from 'react-modal';
import { useState } from 'react';
import { ReactComponent as XCircle } from '../images/xCircle.svg';
import { ReactComponent as CheckCircle } from '../images/checkCircle.svg';
import { ReactComponent as X } from '../images/x.svg';
const modalStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
padding: '18px',
border: '1px solid var(--border)',
borderRadius: '4px',
},
};
Modal.setAppElement('#root');
const TableRow = ({ title, value, issues }) => {
const issuesExist = issues.length !== 0;
const [modalIsOpen, setIsOpen] = useState(false);
const dataValue = !value && value !== 0 ? 'null' : value;
const openModal = () => {
if (issuesExist) setIsOpen(true);
};
const closeModal = () => {
setIsOpen(false);
};
return (
<>
<tr
sx={
issuesExist
? {
cursor: 'pointer',
':hover': {
backgroundColor: 'var(--issueBackground)',
color: 'var(--issueText)',
},
}
: null
}
onClick={openModal}
>
<td>{title}</td>
{value ? (
<>
<td>{Array.isArray(value) ? JSON.stringify(value) : dataValue}</td>
<td>
{issuesExist ? (
<XCircle sx={{ display: 'flex', width: '20px' }} />
) : (
<CheckCircle sx={{ display: 'flex', width: '20px' }} />
)}
</td>
</>
) : null}
</tr>
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
style={modalStyles}
contentLabel="Issues Modal"
>
<div
sx={{
display: 'flex',
justifyContent: 'space-between',
margin: '0 0 6px 0',
height: '20px',
}}
>
<div sx={{ fontWeight: '600' }}>{title} issues</div>
<X
sx={{
fill: 'var(--border)',
display: 'flex',
width: '12px',
cursor: 'pointer',
margin: '0 0 0 12px',
':hover': {
fill: 'var(--grey)',
},
}}
onClick={closeModal}
/>
</div>
<ul>
{issues.filter(Boolean).map((ele, index) => (
<li key={index}>{ele}</li>
))}
</ul>
</Modal>
</>
);
};
export default TableRow;

10
src/components/theme.ts Normal file
View file

@ -0,0 +1,10 @@
import type { Theme } from 'theme-ui';
const theme: Theme = {
breakpoints: ['575px', '1060px'],
colors: {
white90: 'rgb(255, 255, 255, 0.90)',
},
};
export default theme;

View file

Before

Width:  |  Height:  |  Size: 470 B

After

Width:  |  Height:  |  Size: 470 B

BIN
src/images/chrome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
src/images/github.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -3,6 +3,7 @@
preserveAspectRatio="xMidYMid meet"
version="1"
viewBox="0 0 4389 1056"
style="display: block"
>
<g fill="#fff" stroke="none" transform="matrix(.1 0 0 -.1 0 1056)">
<path d="M4890 10554c-19-2-102-11-185-20-814-84-1627-377-2332-841-666-437-1242-1047-1652-1748-345-590-582-1279-670-1950-34-261-44-422-44-710 0-388 28-679 99-1035C523 2160 2153 526 4240 106c360-73 640-99 1040-99 488 0 847 44 1305 160 665 168 1310 477 1860 891 1004 754 1703 1833 1983 3062 146 636 168 1351 61 2010-273 1699-1362 3160-2913 3911-540 261-1091 420-1716 495-100 12-240 17-535 19-220 2-416 1-435-1zm870-978c947-115 1772-494 2460-1130 303-281 615-672 815-1025 491-866 669-1875 504-2861-113-681-390-1322-814-1885-233-310-552-627-869-862-1529-1139-3637-1136-5161 7-391 294-746 669-1012 1070-367 553-599 1170-683 1814-113 869 22 1705 399 2472 103 210 151 294 287 499 316 476 726 886 1199 1199 340 225 629 368 999 495 349 120 681 187 1106 225 112 10 641-3 770-18z"></path>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

Before

Width:  |  Height:  |  Size: 468 B

After

Width:  |  Height:  |  Size: 468 B

View file

Before

Width:  |  Height:  |  Size: 489 B

After

Width:  |  Height:  |  Size: 489 B

16
src/index.js Normal file
View file

@ -0,0 +1,16 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
// import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
// reportWebVitals();

Some files were not shown because too many files have changed in this diff Show more