Merge branch 'dev'
This commit is contained in:
commit
eb2a8e2988
56 changed files with 1549 additions and 1034 deletions
|
|
@ -38,5 +38,7 @@ module.exports = {
|
||||||
'react/prop-types': 'off',
|
'react/prop-types': 'off',
|
||||||
'react/react-in-jsx-scope': 'off',
|
'react/react-in-jsx-scope': 'off',
|
||||||
'no-bitwise': 'off',
|
'no-bitwise': 'off',
|
||||||
|
'react/no-array-index-key': 'off',
|
||||||
|
'dot-notation': 'off',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,13 @@
|
||||||
"bowser": "^2.11.0",
|
"bowser": "^2.11.0",
|
||||||
"crypto-js": "^4.0.0",
|
"crypto-js": "^4.0.0",
|
||||||
"emailjs-com": "^3.1.0",
|
"emailjs-com": "^3.1.0",
|
||||||
|
"html-react-parser": "^1.2.8",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^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-scripts": "4.0.3",
|
||||||
"react-tsparticles": "^1.28.0",
|
"react-webworker": "^2.1.0",
|
||||||
"tslib": "^2.2.0"
|
"tslib": "^2.2.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,23 @@
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
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="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<title>Vytal</title>
|
<title>Vytal</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
14
frontend/public/worker.js
Normal file
14
frontend/public/worker.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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,
|
||||||
|
languages: navigator.languages,
|
||||||
|
};
|
||||||
|
|
||||||
|
postMessage(data);
|
||||||
92
frontend/src/components/App.css
Normal file
92
frontend/src/components/App.css
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
: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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import Particles from 'react-tsparticles';
|
|
||||||
import particlesOptions from '../particles.json';
|
|
||||||
import Github from './Github';
|
import Github from './Github';
|
||||||
import MainColumn from './MainColumn';
|
import MainColumn from './MainColumn';
|
||||||
import '../styles/App.css';
|
import './App.css';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<Github />
|
<Github />
|
||||||
<Particles options={particlesOptions} />
|
<div className="background" />
|
||||||
<MainColumn />
|
<MainColumn />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
5
frontend/src/components/Block.js
Normal file
5
frontend/src/components/Block.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
const ContentBlock = ({ children }) => (
|
||||||
|
<div className="contentBlock">{children}</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ContentBlock;
|
||||||
47
frontend/src/components/Blocks.css
Normal file
47
frontend/src/components/Blocks.css
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
frontend/src/components/Blocks.js
Normal file
48
frontend/src/components/Blocks.js
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
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 ? (
|
||||||
|
<>
|
||||||
|
<div className="centerBlockInner">
|
||||||
|
<BlocksOne workerData={workerData} />
|
||||||
|
</div>
|
||||||
|
<div className="centerBlockInner">
|
||||||
|
<BlocksTwo
|
||||||
|
workerData={workerData}
|
||||||
|
connectionData={connectionData}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="centerBlockMobile">
|
||||||
|
<BlocksOne workerData={workerData} />
|
||||||
|
<BlocksTwo
|
||||||
|
workerData={workerData}
|
||||||
|
connectionData={connectionData}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="contentBlock loadBlock">
|
||||||
|
<center>Loading...</center>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Blocks;
|
||||||
15
frontend/src/components/BlocksOne.js
Normal file
15
frontend/src/components/BlocksOne.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import UserAgentBlock from './UserAgentBlock';
|
||||||
|
import IntlBlock from './IntlBlock';
|
||||||
|
import NavigatorBlock from './NavigatorBlock';
|
||||||
|
import FingerprintBlock from './FingerprintBlock';
|
||||||
|
|
||||||
|
const BlocksOne = ({ workerData }) => (
|
||||||
|
<>
|
||||||
|
<FingerprintBlock workerData={workerData} />
|
||||||
|
<NavigatorBlock workerData={workerData} />
|
||||||
|
<UserAgentBlock workerAgent={workerData.userAgent} />
|
||||||
|
<IntlBlock workerData={workerData} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default BlocksOne;
|
||||||
15
frontend/src/components/BlocksTwo.js
Normal file
15
frontend/src/components/BlocksTwo.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import OtherBlock from './OtherBlock';
|
||||||
|
import ScreenBlock from './ScreenBlock';
|
||||||
|
import LocationBlock from './LocationBlock';
|
||||||
|
import ConnectionBlock from './ConnectionBlock';
|
||||||
|
|
||||||
|
const BlocksTwo = ({ workerData, connectionData }) => (
|
||||||
|
<>
|
||||||
|
<LocationBlock workerData={workerData} connectionData={connectionData} />
|
||||||
|
<ConnectionBlock workerData={workerData} connectionData={connectionData} />
|
||||||
|
<ScreenBlock />
|
||||||
|
<OtherBlock workerData={workerData} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default BlocksTwo;
|
||||||
|
|
@ -1,32 +1,25 @@
|
||||||
import { useState, useEffect } from 'react';
|
import Block from './Block';
|
||||||
import ScanBlock from './ScanBlock';
|
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
import { fetchAPI, getConnection } from './main';
|
import { getConnection } from '../utils/connection';
|
||||||
|
|
||||||
const ConnectionBlock = () => {
|
const LocationBlock = ({ connectionData }) => (
|
||||||
const [data, setData] = useState('');
|
<Block>
|
||||||
const [display, setDisplay] = useState('');
|
<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>
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
export default LocationBlock;
|
||||||
fetchAPI(setData, setDisplay);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScanBlock>
|
|
||||||
<h1>Connection</h1>
|
|
||||||
{display === 1 && <Table data={getConnection(data)} />}
|
|
||||||
{display === 0 && (
|
|
||||||
<div className="boxWrap">
|
|
||||||
Unable to fetch info. Adblock or content filter may have prevented
|
|
||||||
data from loading.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<p>
|
|
||||||
<b>Explanation:</b> Your IP address reveals information about your
|
|
||||||
connection. Using a VPN or Tor will hide your connection info.
|
|
||||||
</p>
|
|
||||||
</ScanBlock>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ConnectionBlock;
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import { ReactComponent as WifiIcon } from '../images/wifi.svg';
|
|
||||||
import { ReactComponent as BrowserIcon } from '../images/browser.svg';
|
|
||||||
import { ReactComponent as FingerprintIcon } from '../images/fingerprint.svg';
|
|
||||||
|
|
||||||
const Icons = {
|
|
||||||
wifi: <WifiIcon />,
|
|
||||||
browser: <BrowserIcon />,
|
|
||||||
fingerprint: <FingerprintIcon />,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ContentList = ({ items }) => (
|
|
||||||
<div className="contentList">
|
|
||||||
{items.map((item) => (
|
|
||||||
<div className="contentItem" key={item.title}>
|
|
||||||
<div className="contentIcon">{Icons[item.icon]}</div>
|
|
||||||
<div className="contentText">
|
|
||||||
<h2>{item.title}</h2>
|
|
||||||
<div className="contentBody">{item.body}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ContentList;
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import ScanBlock from './ScanBlock';
|
|
||||||
import Table from './Table';
|
|
||||||
|
|
||||||
const FiltersBlock = () => {
|
|
||||||
const [adBlockDetected, setAdBlockDetected] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetch('https://www3.doubleclick.net', {
|
|
||||||
method: 'HEAD',
|
|
||||||
mode: 'no-cors',
|
|
||||||
cache: 'no-store',
|
|
||||||
}).catch(() => {
|
|
||||||
setAdBlockDetected(true);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
key: 'adBlock',
|
|
||||||
title: 'Adblock detected',
|
|
||||||
value: adBlockDetected ? 'True' : 'False',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScanBlock>
|
|
||||||
<h1>Content Filters</h1>
|
|
||||||
<Table data={data} />
|
|
||||||
<p>
|
|
||||||
<b>Explanation:</b> Although content filters like adblock can protect
|
|
||||||
you against certain methods of tracking, it can also be used as another
|
|
||||||
identification metric.
|
|
||||||
</p>
|
|
||||||
</ScanBlock>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FiltersBlock;
|
|
||||||
50
frontend/src/components/FingerprintBlock.css
Normal file
50
frontend/src/components/FingerprintBlock.css
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.saveButton:hover {
|
||||||
|
background-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text'] {
|
||||||
|
border: 1px solid var(--grey);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px;
|
||||||
|
width: 200px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
.boxWrap {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text'] {
|
||||||
|
width: calc(100% - 70px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,29 +1,32 @@
|
||||||
import { useState } from 'react';
|
import './FingerprintBlock.css';
|
||||||
import ScanBlock from './ScanBlock';
|
import { useState, useEffect } from 'react';
|
||||||
|
import Block from './Block';
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
import {
|
import {
|
||||||
getHardware,
|
getSignature,
|
||||||
getWebGL,
|
postSignature,
|
||||||
getSoftware,
|
|
||||||
getFingerprint,
|
|
||||||
getHash,
|
getHash,
|
||||||
getName,
|
getFingerprint,
|
||||||
handleSave,
|
} from '../utils/fingerprint';
|
||||||
} from './main';
|
|
||||||
|
const FingerprintBlock = ({ workerData }) => {
|
||||||
|
const [signature, setSignature] = useState();
|
||||||
|
const [load, setload] = useState(false);
|
||||||
|
const hash = getHash(workerData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getSignature(hash, setSignature, setload);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const FingerprintBlock = () => {
|
|
||||||
const [name, setName] = useState('');
|
|
||||||
const [load, setLoad] = useState(false);
|
|
||||||
const [saved, setSaved] = useState('');
|
|
||||||
const hash = getHash([...getHardware(), ...getWebGL(), ...getSoftware()]);
|
|
||||||
getName(hash, setName, setLoad);
|
|
||||||
return (
|
return (
|
||||||
<ScanBlock>
|
<Block>
|
||||||
<h1>Fingerprint</h1>
|
<h1>Fingerprint</h1>
|
||||||
{load && (
|
{load && (
|
||||||
<>
|
<>
|
||||||
{name ? (
|
{signature !== undefined ? (
|
||||||
<Table data={getFingerprint(name, hash)} />
|
<div className="fingerprintTable">
|
||||||
|
<Table data={getFingerprint(signature, hash)} />
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="boxWrap">
|
<div className="boxWrap">
|
||||||
<div className="hash">{hash}</div>
|
<div className="hash">{hash}</div>
|
||||||
|
|
@ -32,24 +35,25 @@ const FingerprintBlock = () => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<p>
|
<p>
|
||||||
<b>Explanation:</b> This is a unique identifier that can be used to
|
<b>Explanation:</b> This hash is calculated from your device data. Even
|
||||||
follow you around the web. Even if you clear cookies, change your IP or
|
if you clear cookies, change your IP or use private mode the hash will
|
||||||
use private mode the hash will stay the same. Enter your name below and
|
stay the same. Enter a signature and turn on a VPN to test it out.
|
||||||
reload the page in private mode to test it out.
|
|
||||||
</p>
|
</p>
|
||||||
{saved ? (
|
<form onSubmit={(e) => postSignature(hash, e.target[0].value)}>
|
||||||
<p>Success! Re-scan browser.</p>
|
<input
|
||||||
) : (
|
type="text"
|
||||||
<form
|
id="signature"
|
||||||
onSubmit={(e) => {
|
name="signature"
|
||||||
handleSave(e, hash, setSaved);
|
placeholder="Enter signature"
|
||||||
}}
|
/>
|
||||||
>
|
<input
|
||||||
<input type="text" id="name" name="name" placeholder="Enter name" />
|
type="submit"
|
||||||
<input type="submit" id="saveButton" value="Save" maxLength="100" />
|
className="saveButton"
|
||||||
</form>
|
value="Save"
|
||||||
)}
|
maxLength="100"
|
||||||
</ScanBlock>
|
/>
|
||||||
|
</form>
|
||||||
|
</Block>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import ScanBlock from './ScanBlock';
|
import Block from './Block';
|
||||||
import fontList from '../fontList.json';
|
import fontList from '../fontList.json';
|
||||||
|
|
||||||
const FontsBlock = () => {
|
const FontsBlock = () => {
|
||||||
|
|
@ -45,7 +45,7 @@ const FontsBlock = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScanBlock>
|
<Block>
|
||||||
<h1>System Fonts</h1>
|
<h1>System Fonts</h1>
|
||||||
<div className="fonts boxWrap" />
|
<div className="fonts boxWrap" />
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -54,7 +54,7 @@ const FontsBlock = () => {
|
||||||
fonts that you have installed that aren't common for your system
|
fonts that you have installed that aren't common for your system
|
||||||
can make you easily identifiable.
|
can make you easily identifiable.
|
||||||
</p>
|
</p>
|
||||||
</ScanBlock>
|
</Block>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
18
frontend/src/components/GitHub.css
Normal file
18
frontend/src/components/GitHub.css
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { ReactComponent as GithubImg } from '../images/github.svg';
|
import './GitHub.css';
|
||||||
|
import GitHubButton from 'react-github-btn';
|
||||||
|
|
||||||
const Github = () => (
|
const Github = () => (
|
||||||
<a
|
<div className="gitHubButton">
|
||||||
href="https://github.com/z0ccc/Vytal"
|
<GitHubButton
|
||||||
className="github"
|
href="https://github.com/z0ccc/Vytal"
|
||||||
target="_blank"
|
data-color-scheme="no-preference: light; light: light; dark: light;"
|
||||||
rel="noreferrer"
|
aria-label="Star z0ccc/Vytal on GitHub"
|
||||||
alt="Link to Github page"
|
>
|
||||||
>
|
Star
|
||||||
<GithubImg />
|
</GitHubButton>
|
||||||
</a>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Github;
|
export default Github;
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import ScanBlock from './ScanBlock';
|
|
||||||
import Table from './Table';
|
|
||||||
import { getHardware, getWebGL, getBattery } from './main';
|
|
||||||
|
|
||||||
const HardwareBlock = () => {
|
|
||||||
const [data, setData] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getBattery().then((batteryInfo) => {
|
|
||||||
setData([...getHardware(), ...getWebGL(), ...batteryInfo]);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScanBlock>
|
|
||||||
<h1>Hardware</h1>
|
|
||||||
<Table data={data} />
|
|
||||||
<p>
|
|
||||||
<b>Explanation:</b> JavaScript can be used to find information about
|
|
||||||
your hardware. This information can be used to create a fingerprint.
|
|
||||||
</p>
|
|
||||||
</ScanBlock>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default HardwareBlock;
|
|
||||||
24
frontend/src/components/IntlBlock.js
Normal file
24
frontend/src/components/IntlBlock.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import Block from './Block';
|
||||||
|
import Table from './Table';
|
||||||
|
import getIntl from '../utils/intl';
|
||||||
|
|
||||||
|
const IntlBlock = ({ workerData }) => (
|
||||||
|
<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;
|
||||||
|
|
@ -1,37 +1,26 @@
|
||||||
import { useState, useEffect } from 'react';
|
import Block from './Block';
|
||||||
import ScanBlock from './ScanBlock';
|
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
import { fetchAPI, getLocation, getMap } from './main';
|
import { getMap, getLocation } from '../utils/connection';
|
||||||
|
|
||||||
const LocationBlock = () => {
|
const LocationBlock = ({ connectionData, workerData }) => (
|
||||||
const [data, setData] = useState([]);
|
<Block>
|
||||||
const [display, setDisplay] = useState('');
|
<h1>Location</h1>
|
||||||
|
<img src={getMap(connectionData)} alt="Map of current location" />
|
||||||
useEffect(() => {
|
<Table data={getLocation(connectionData, workerData)} />
|
||||||
fetchAPI(setData, setDisplay);
|
<p>
|
||||||
}, []);
|
<b>Explanation:</b> Your IP address can be used to determine your
|
||||||
|
location.{' '}
|
||||||
return (
|
<a
|
||||||
<ScanBlock>
|
className="link"
|
||||||
<h1>Location</h1>
|
target="_blank"
|
||||||
{display === 1 && (
|
rel="noreferrer"
|
||||||
<>
|
alt="Read more about ip location"
|
||||||
<img src={getMap(data)} alt="Map of current location" />
|
href="https://en.wikipedia.org/wiki/IP_address"
|
||||||
<Table data={getLocation(data)} />
|
>
|
||||||
</>
|
Read more
|
||||||
)}
|
</a>
|
||||||
{display === 0 && (
|
</p>
|
||||||
<div className="boxWrap">
|
</Block>
|
||||||
Unable to fetch info. Adblock or content filter may have prevented
|
);
|
||||||
data from loading.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<p>
|
|
||||||
<b>Explanation:</b> Your IP address can be used to determine your
|
|
||||||
location. Using a VPN or Tor will hide your true location.
|
|
||||||
</p>
|
|
||||||
</ScanBlock>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LocationBlock;
|
export default LocationBlock;
|
||||||
|
|
|
||||||
18
frontend/src/components/Logo.css
Normal file
18
frontend/src/components/Logo.css
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
.logoWrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin: 20px 0;
|
||||||
|
width: 250px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
.logo {
|
||||||
|
width: 160px;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import './Logo.css';
|
||||||
import { ReactComponent as LogoImg } from '../images/logo.svg';
|
import { ReactComponent as LogoImg } from '../images/logo.svg';
|
||||||
|
|
||||||
const Logo = () => (
|
const Logo = () => (
|
||||||
|
|
|
||||||
5
frontend/src/components/MainColumn.css
Normal file
5
frontend/src/components/MainColumn.css
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
.centerBlockOuter {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,13 @@
|
||||||
import { useState } from 'react';
|
import './MainColumn.css';
|
||||||
import Logo from './Logo';
|
import Logo from './Logo';
|
||||||
import StartBlock from './StartBlock';
|
import Blocks from './Blocks';
|
||||||
import ScanBlocks from './ScanBlocks';
|
|
||||||
|
|
||||||
const MainColumn = () => {
|
const MainColumn = () => (
|
||||||
const [scan, setScan] = useState(false);
|
<>
|
||||||
return (
|
<Logo />
|
||||||
<div className="centerBlockOuter">
|
<div className="centerBlockOuter">
|
||||||
<div className="centerBlockInner">
|
<Blocks />
|
||||||
<Logo />
|
|
||||||
{scan ? <ScanBlocks /> : <StartBlock scan={scan} setScan={setScan} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</>
|
||||||
};
|
);
|
||||||
export default MainColumn;
|
export default MainColumn;
|
||||||
|
|
|
||||||
25
frontend/src/components/NavigatorBlock.js
Normal file
25
frontend/src/components/NavigatorBlock.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Block from './Block';
|
||||||
|
import Table from './Table';
|
||||||
|
import getNavigator from '../utils/navigator';
|
||||||
|
|
||||||
|
const NavigatorBlock = ({ workerData }) => (
|
||||||
|
<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;
|
||||||
42
frontend/src/components/OtherBlock.js
Normal file
42
frontend/src/components/OtherBlock.js
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import Block from './Block';
|
||||||
|
import Table from './Table';
|
||||||
|
import getOther from '../utils/other';
|
||||||
|
|
||||||
|
const OtherBlock = ({ workerData }) => {
|
||||||
|
const [adBlock, setAdBlock] = useState();
|
||||||
|
const [battery, setBattery] = useState();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
const ContentBlock = ({ children }) => (
|
|
||||||
<div className="contentBlock">
|
|
||||||
<div className="contentItem">
|
|
||||||
<div className="contentText">{children}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ContentBlock;
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import FingerprintBlock from './FingerprintBlock';
|
|
||||||
import LocationBlock from './LocationBlock';
|
|
||||||
import HardwareBlock from './HardwareBlock';
|
|
||||||
import SoftwareBlock from './SoftwareBlock';
|
|
||||||
import ConnectionBlock from './ConnectionBlock';
|
|
||||||
import FiltersBlock from './FiltersBlock';
|
|
||||||
// import FontsBlock from './FontsBlock';
|
|
||||||
|
|
||||||
const ScanBlocks = () => (
|
|
||||||
<>
|
|
||||||
<FingerprintBlock />
|
|
||||||
<LocationBlock />
|
|
||||||
<ConnectionBlock />
|
|
||||||
<FiltersBlock />
|
|
||||||
<SoftwareBlock />
|
|
||||||
<HardwareBlock />
|
|
||||||
{/* <FontsBlock /> */}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ScanBlocks;
|
|
||||||
24
frontend/src/components/ScreenBlock.js
Normal file
24
frontend/src/components/ScreenBlock.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
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;
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import ScanBlock from './ScanBlock';
|
|
||||||
import Table from './Table';
|
|
||||||
import { getSoftware } from './main';
|
|
||||||
|
|
||||||
const SoftwareBlock = () => (
|
|
||||||
<ScanBlock>
|
|
||||||
<h1>Software</h1>
|
|
||||||
<Table data={getSoftware()} />
|
|
||||||
<p>
|
|
||||||
<b>Explanation:</b> JavaScript can be used to find information about your
|
|
||||||
software. This information can be used to create a fingerprint.
|
|
||||||
</p>
|
|
||||||
</ScanBlock>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default SoftwareBlock;
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import ContentList from './ContentList';
|
|
||||||
import ScanBlock from './ScanBlock';
|
|
||||||
|
|
||||||
const contentItems = [
|
|
||||||
{
|
|
||||||
title: 'Fingerprint',
|
|
||||||
icon: 'fingerprint',
|
|
||||||
body: 'Browsers reveal bits of identifiable information. This data can be combined into a digital fingerprint which can be used to follow you around the web.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Connection',
|
|
||||||
icon: 'wifi',
|
|
||||||
body: 'Websites are able to access your IP address when you connect to their server. Your IP address exposes information about your connection and location.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'System Info',
|
|
||||||
icon: 'browser',
|
|
||||||
body: "JavaScript can be used to find data about your computer's software and hardware. This information can be used to create a fingerprint.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const StartBlock = ({ setScan }) => (
|
|
||||||
<ScanBlock>
|
|
||||||
<h2>About</h2>
|
|
||||||
<div className="contentBody">
|
|
||||||
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 private mode.
|
|
||||||
</div>
|
|
||||||
<ContentList items={contentItems} />
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
onClick={() => setScan(true)}
|
|
||||||
id="scanButton"
|
|
||||||
value="Scan Browser"
|
|
||||||
/>
|
|
||||||
</ScanBlock>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default StartBlock;
|
|
||||||
14
frontend/src/components/Table.css
Normal file
14
frontend/src/components/Table.css
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
|
import './Table.css';
|
||||||
|
import TableRow from './TableRow';
|
||||||
|
|
||||||
const Table = ({ data }) => (
|
const Table = ({ data }) => (
|
||||||
<div className="tableWrap">
|
<div className="tableWrap">
|
||||||
<table>
|
<table>
|
||||||
{data.map((item) => (
|
{data.map((item) => (
|
||||||
<tbody key={item.title}>
|
<tbody key={item.key} title={item.code}>
|
||||||
<tr>
|
<TableRow item={item} />
|
||||||
<td>{item.title}</td>
|
|
||||||
<td>{item.value}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
))}
|
))}
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
72
frontend/src/components/TableRow.css
Normal file
72
frontend/src/components/TableRow.css
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalTitle {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
fill: var(--border);
|
||||||
|
display: flex;
|
||||||
|
width: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: -12px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton:hover {
|
||||||
|
fill: var(--grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
td {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-child {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:nth-child(3) {
|
||||||
|
width: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
68
frontend/src/components/TableRow.js
Normal file
68
frontend/src/components/TableRow.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
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;
|
||||||
25
frontend/src/components/UserAgentBlock.js
Normal file
25
frontend/src/components/UserAgentBlock.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Block from './Block';
|
||||||
|
import Table from './Table';
|
||||||
|
import getUserAgent from '../utils/userAgent';
|
||||||
|
|
||||||
|
const UserAgentBlock = ({ workerAgent }) => (
|
||||||
|
<Block>
|
||||||
|
<h1>User Agent</h1>
|
||||||
|
<Table data={getUserAgent(workerAgent)} />
|
||||||
|
<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;
|
||||||
|
|
@ -1,349 +0,0 @@
|
||||||
import md5 from 'crypto-js/md5';
|
|
||||||
import Bowser from 'bowser';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
export {
|
|
||||||
fetchAPI,
|
|
||||||
getLocation,
|
|
||||||
getMap,
|
|
||||||
getConnection,
|
|
||||||
detectTor,
|
|
||||||
getSoftware,
|
|
||||||
getHardware,
|
|
||||||
getWebGL,
|
|
||||||
getBattery,
|
|
||||||
getFingerprint,
|
|
||||||
getHash,
|
|
||||||
getName,
|
|
||||||
handleSave,
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchAPI = (setData, setDisplay) => {
|
|
||||||
fetch('https://api.vytal.io/ip/')
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((json) => {
|
|
||||||
setData(json);
|
|
||||||
setDisplay(1);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setDisplay(0);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getLocation = (json) => {
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
key: 'country',
|
|
||||||
title: 'Country',
|
|
||||||
value: json.country,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'regionName',
|
|
||||||
title: 'Region',
|
|
||||||
value: json.regionName,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'lat',
|
|
||||||
title: 'City',
|
|
||||||
value: json.city,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'zip',
|
|
||||||
title: 'Zip code',
|
|
||||||
value: json.zip,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'lat',
|
|
||||||
title: 'Latitude',
|
|
||||||
value: json.lat,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'lon',
|
|
||||||
title: 'Longitude',
|
|
||||||
value: json.lon,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 getConnection = (json) => {
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
key: 'ipAddress',
|
|
||||||
title: 'IP address',
|
|
||||||
value: json.query,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'isp',
|
|
||||||
title: 'ISP',
|
|
||||||
value: json.isp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'org',
|
|
||||||
title: 'Organization',
|
|
||||||
value: json.org,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'asn',
|
|
||||||
title: 'ASN',
|
|
||||||
value: json.as,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'tor',
|
|
||||||
title: 'Tor browser detected',
|
|
||||||
value: detectTor() ? 'True' : 'False',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 getHardware = () => {
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
key: 'screenResolution',
|
|
||||||
title: 'Screen resolution',
|
|
||||||
value: `${window.screen.width}x${window.screen.height}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'colorResolution',
|
|
||||||
title: 'Color Resolution',
|
|
||||||
value: window.screen.colorDepth,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'deviceMemory',
|
|
||||||
title: 'Device memory',
|
|
||||||
value: navigator.deviceMemory ? `${navigator.deviceMemory}GB` : 'N/A',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'cpuCores',
|
|
||||||
title: '# of CPU cores',
|
|
||||||
value: navigator.hardwareConcurrency || 'N/A',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'maxTouchpoints',
|
|
||||||
title: 'Max touchpoints',
|
|
||||||
value: navigator.maxTouchPoints || 0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getBattery = async () => {
|
|
||||||
let level, status;
|
|
||||||
if ('getBattery' in navigator) {
|
|
||||||
await navigator.getBattery().then((res) => {
|
|
||||||
level = `${Math.round(res.level * 100)}%`;
|
|
||||||
status = res.charging ? 'Charging' : 'Not charging';
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
level = 'N/A';
|
|
||||||
status = 'N/A';
|
|
||||||
}
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
key: 'batteryLevel',
|
|
||||||
title: 'Battery level',
|
|
||||||
value: level,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'batteryStatus',
|
|
||||||
title: 'Battery status',
|
|
||||||
value: status,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getWebGL = () => {
|
|
||||||
const gl = document.createElement('canvas').getContext('webgl');
|
|
||||||
let ext;
|
|
||||||
if (gl) {
|
|
||||||
ext = gl.getExtension('WEBGL_debug_renderer_info');
|
|
||||||
} else {
|
|
||||||
ext = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
key: 'webGLVendor',
|
|
||||||
title: 'WebGL vendor',
|
|
||||||
value: ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : 'N/A',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'webglRenderer',
|
|
||||||
title: 'WebGL renderer',
|
|
||||||
value: ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : 'N/A',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSoftware = () => {
|
|
||||||
const uaResult = Bowser.parse(navigator.userAgent);
|
|
||||||
const date = new Date();
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
key: 'browser',
|
|
||||||
title: 'Browser',
|
|
||||||
value: uaResult.browser.name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'browserVersion',
|
|
||||||
title: 'Browser version',
|
|
||||||
value: uaResult.browser.version,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'browserEngine',
|
|
||||||
title: 'Browser engine',
|
|
||||||
value: uaResult.browser.engine || 'N/A',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'os',
|
|
||||||
title: 'OS',
|
|
||||||
value: `${uaResult.os.name} ${uaResult.os.versionName}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'osVersion',
|
|
||||||
title: 'OS version',
|
|
||||||
value: uaResult.os.version,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'platform',
|
|
||||||
title: 'Platform',
|
|
||||||
value: navigator.platform,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'systemType',
|
|
||||||
title: 'System type',
|
|
||||||
value: uaResult.platform.type,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'userAgent',
|
|
||||||
title: 'User agent',
|
|
||||||
value: navigator.userAgent || 'N/A',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'preferredLanguage',
|
|
||||||
title: 'Preferred language',
|
|
||||||
value: navigator.language || 'N/A',
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// key: 'languages',
|
|
||||||
// title: 'Languages',
|
|
||||||
// value: sortArr(navigator.languages) || 'N/A',
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
key: 'timezone',
|
|
||||||
title: 'Timezone',
|
|
||||||
value: Intl.DateTimeFormat().resolvedOptions().timeZone || 'N/A',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'timezoneOffset',
|
|
||||||
title: 'Timezone offset',
|
|
||||||
value: date.getTimezoneOffset() || 'N/A',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'cookiesEnabled',
|
|
||||||
title: 'Cookies enabled',
|
|
||||||
value: navigator.cookieEnabled ? 'True' : 'False',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'javaEnabled',
|
|
||||||
title: 'Java enabled',
|
|
||||||
value: navigator.javaEnabled() ? 'True' : 'False',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'dntHeader',
|
|
||||||
title: 'DNT header enabled',
|
|
||||||
value: navigator.doNotTrack ? 'True' : 'False',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'automatedBrowser',
|
|
||||||
title: 'Automated browser',
|
|
||||||
value: navigator.webdriver ? 'True' : 'False',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'plugins',
|
|
||||||
title: 'Plugins',
|
|
||||||
value: sortPlugins(navigator.plugins) || 'N/A',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 getFingerprint = (name, hash) => {
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
key: 'name',
|
|
||||||
title: 'Name',
|
|
||||||
value: name,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'hash',
|
|
||||||
title: 'Hash',
|
|
||||||
value: hash,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getHash = (data) => md5(JSON.stringify(data)).toString();
|
|
||||||
|
|
||||||
const getName = (hash, setName, setLoad) => {
|
|
||||||
axios
|
|
||||||
.get(`https://api.vytal.io/fingerprint/?hash=${hash}`)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.data.length !== 0) {
|
|
||||||
setName(response.data[response.data.length - 1].name);
|
|
||||||
}
|
|
||||||
setLoad(true);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSave = (e, hash, setSaved) => {
|
|
||||||
e.preventDefault();
|
|
||||||
axios.post('https://api.vytal.io/fingerprint/', {
|
|
||||||
name: e.target[0].value,
|
|
||||||
hash,
|
|
||||||
});
|
|
||||||
setSaved(true);
|
|
||||||
};
|
|
||||||
5
frontend/src/images/checkCircle.svg
Normal file
5
frontend/src/images/checkCircle.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<g fill="#c3e6cb">
|
||||||
|
<path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 470 B |
|
|
@ -1,5 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
|
|
||||||
<g fill="#fff">
|
|
||||||
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB |
1
frontend/src/images/x.svg
Normal file
1
frontend/src/images/x.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>
|
||||||
|
After Width: | Height: | Size: 468 B |
5
frontend/src/images/xCircle.svg
Normal file
5
frontend/src/images/xCircle.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<g fill="#f4c1c6">
|
||||||
|
<path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 489 B |
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import './styles/index.css';
|
|
||||||
import App from './components/App';
|
import App from './components/App';
|
||||||
import * as serviceWorker from './serviceWorker';
|
import * as serviceWorker from './serviceWorker';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
{
|
|
||||||
"fpsLimit": 30,
|
|
||||||
"interactivity": {
|
|
||||||
"detectsOn": "window",
|
|
||||||
"events": {
|
|
||||||
"onClick": {
|
|
||||||
"enable": false,
|
|
||||||
"mode": "push"
|
|
||||||
},
|
|
||||||
"onHover": {
|
|
||||||
"enable": true,
|
|
||||||
"mode": "bubble"
|
|
||||||
},
|
|
||||||
"resize": true
|
|
||||||
},
|
|
||||||
"modes": {
|
|
||||||
"bubble": {
|
|
||||||
"distance": 150,
|
|
||||||
"duration": 2,
|
|
||||||
"opacity": 0.5,
|
|
||||||
"size": 15
|
|
||||||
},
|
|
||||||
"push": {
|
|
||||||
"quantity": 4
|
|
||||||
},
|
|
||||||
"repulse": {
|
|
||||||
"distance": 200,
|
|
||||||
"duration": 0.4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"particles": {
|
|
||||||
"color": {
|
|
||||||
"value": "#ffffff"
|
|
||||||
},
|
|
||||||
"links": {
|
|
||||||
"color": "#ffffff",
|
|
||||||
"distance": 150,
|
|
||||||
"enable": true,
|
|
||||||
"opacity": 0.2,
|
|
||||||
"width": 1
|
|
||||||
},
|
|
||||||
"collisions": {
|
|
||||||
"enable": true
|
|
||||||
},
|
|
||||||
"move": {
|
|
||||||
"direction": "none",
|
|
||||||
"enable": true,
|
|
||||||
"outMode": "bounce",
|
|
||||||
"random": false,
|
|
||||||
"speed": 0.2,
|
|
||||||
"straight": false
|
|
||||||
},
|
|
||||||
"number": {
|
|
||||||
"density": {
|
|
||||||
"enable": true,
|
|
||||||
"value_area": 800
|
|
||||||
},
|
|
||||||
"value": 70
|
|
||||||
},
|
|
||||||
"opacity": {
|
|
||||||
"value": 0.4
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"type": "circle"
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"random": true,
|
|
||||||
"value": 5
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"detectRetina": true
|
|
||||||
}
|
|
||||||
|
|
@ -1,261 +0,0 @@
|
||||||
:root {
|
|
||||||
--main: #943ec5;
|
|
||||||
--grey: #9fa6b2;
|
|
||||||
--text: #4b5563;
|
|
||||||
--border: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tsparticles {
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: -1;
|
|
||||||
background: rgb(87, 35, 117);
|
|
||||||
background: linear-gradient(
|
|
||||||
165deg,
|
|
||||||
rgba(87, 35, 117, 1) 0%,
|
|
||||||
rgba(148, 62, 197, 1) 55%,
|
|
||||||
rgba(211, 176, 231, 1) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.github {
|
|
||||||
width: 36px;
|
|
||||||
position: fixed;
|
|
||||||
top: 12px;
|
|
||||||
right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.centerBlockOuter {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex: 1 0 auto;
|
|
||||||
margin: 0 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.centerBlockInner {
|
|
||||||
width: 650px;
|
|
||||||
margin: 24px 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logoWrap {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: 270px;
|
|
||||||
margin: 0 0 14px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentItem {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentItem:not(:last-child) {
|
|
||||||
margin: 0 0 24px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentIcon {
|
|
||||||
flex: none;
|
|
||||||
margin: 0 24px 0 0;
|
|
||||||
width: 32px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentText {
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin: 0 0 12px 0;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 19px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin: 0 0 4px 0;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentList {
|
|
||||||
margin: 24px 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#scanButton {
|
|
||||||
display: block;
|
|
||||||
background-color: var(--main);
|
|
||||||
color: #fff;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 24px 0 0 0;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
#scanButton:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.boxWrap {
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tableWrap {
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
table-layout: fixed;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody:not(:last-child) {
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:hover {
|
|
||||||
color: var(--main);
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
vertical-align: top;
|
|
||||||
padding: 12px;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:first-child {
|
|
||||||
width: 180px;
|
|
||||||
font-weight: 600;
|
|
||||||
word-break: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hash {
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
margin: 12px 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type='text'] {
|
|
||||||
border: 1px solid var(--grey);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 6px;
|
|
||||||
width: 200px;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#saveButton {
|
|
||||||
border: 1px solid var(--grey);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 6px;
|
|
||||||
background-color: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--text);
|
|
||||||
margin: 0 0 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#saveButton:hover {
|
|
||||||
background-color: var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
|
||||||
.github {
|
|
||||||
width: 24px;
|
|
||||||
position: absolute;
|
|
||||||
top: 8px;
|
|
||||||
right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentBlock {
|
|
||||||
padding: 18px;
|
|
||||||
margin: 0 0 18px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.centerBlockInner {
|
|
||||||
width: 650px;
|
|
||||||
margin: 12px 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: 185px;
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin: 0 0 3px 0;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentIcon {
|
|
||||||
flex: none;
|
|
||||||
margin: 0 18px 0 0;
|
|
||||||
width: 28px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:first-child {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.boxWrap {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type='text'] {
|
|
||||||
width: calc(100% - 65px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
||||||
25
frontend/src/utils/common.js
Normal file
25
frontend/src/utils/common.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
export { fetchAPI, checkWebWorker, getWebWorker };
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
};
|
||||||
88
frontend/src/utils/connection.js
Normal file
88
frontend/src/utils/connection.js
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
export { getMap, getConnection, getLocation };
|
||||||
|
|
||||||
|
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: 'Organization',
|
||||||
|
value: data.org,
|
||||||
|
issues: [isProxy],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ASN',
|
||||||
|
value: data.as,
|
||||||
|
issues: [isProxy],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
37
frontend/src/utils/fingerprint.js
Normal file
37
frontend/src/utils/fingerprint.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import md5 from 'crypto-js/md5';
|
||||||
|
|
||||||
|
export { getSignature, postSignature, getHash, getFingerprint };
|
||||||
|
|
||||||
|
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, signature) => {
|
||||||
|
axios.post('https://api.vytal.io/fingerprint/', {
|
||||||
|
name: signature,
|
||||||
|
hash,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHash = (data) => md5(JSON.stringify(data)).toString();
|
||||||
|
|
||||||
|
const getFingerprint = (signature, hash) => [
|
||||||
|
{
|
||||||
|
key: 'Signature',
|
||||||
|
value: signature,
|
||||||
|
issues: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Hash',
|
||||||
|
value: hash,
|
||||||
|
issues: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
26
frontend/src/utils/intl.js
Normal file
26
frontend/src/utils/intl.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
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;
|
||||||
210
frontend/src/utils/navigator.js
Normal file
210
frontend/src/utils/navigator.js
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
import { checkWebWorker } from './common';
|
||||||
|
|
||||||
|
const getDeviceMemory = (key, worker) => ({
|
||||||
|
key: 'Device memory',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key],
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
checkWebWorker(navigator[key], worker),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getHardwareConcurrency = (key, worker) => ({
|
||||||
|
key: 'Hardware concurrency',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key],
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
checkWebWorker(navigator[key], worker),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getMaxTouchPoints = (key) => ({
|
||||||
|
key: 'Max touch points',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key],
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getPlatform = (key, worker) => ({
|
||||||
|
key: 'Platform',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key],
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
checkWebWorker(navigator[key], worker),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getUserAgent = (key, worker) => ({
|
||||||
|
key: 'User agent',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key],
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
checkWebWorker(navigator[key], worker),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAppVersion = (key, worker) => ({
|
||||||
|
key: 'App version',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key],
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
checkWebWorker(navigator[key], worker),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getLanguage = (key, worker) => ({
|
||||||
|
key: 'Language',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key],
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
checkWebWorker(navigator[key], worker),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getLanguages = (key, worker) => ({
|
||||||
|
key: 'Languages',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key],
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
checkWebWorker(navigator[key], worker),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCookieEnabled = (key) => ({
|
||||||
|
key: 'Cookie enabled',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key] ? 'True' : 'False',
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDoNotTrack = (key) => ({
|
||||||
|
key: 'Do not track',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key] ? 'True' : 'False',
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getWebDriver = (key) => ({
|
||||||
|
key: 'Web driver',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key] ? 'True' : 'False',
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getPlugins = (key) => ({
|
||||||
|
key: 'Plugins',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: sortPlugins(navigator[key]),
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getVendor = (key) => ({
|
||||||
|
key: 'Vendor',
|
||||||
|
code: `navigator.${key}`,
|
||||||
|
value: navigator[key],
|
||||||
|
issues: [
|
||||||
|
checkNavigatorProperties(key),
|
||||||
|
checkNavigatorValue(key),
|
||||||
|
checkNavigatorPrototype(key),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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 = (key) => {
|
||||||
|
if (Object.getOwnPropertyDescriptor(navigator, key) !== undefined) {
|
||||||
|
return 'Failed undefined properties';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkNavigatorValue = (key) => {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const { value } = Object.getOwnPropertyDescriptor(Navigator.prototype, key);
|
||||||
|
} catch (err) {
|
||||||
|
return 'Failed Navigator.prototype';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkNavigatorPrototype = (key) => {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const check = Navigator.prototype[key];
|
||||||
|
return 'Failed Navigator.prototype';
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const check = '';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNavigator = (workerData) => [
|
||||||
|
getDeviceMemory('deviceMemory', workerData.deviceMemory),
|
||||||
|
getHardwareConcurrency('hardwareConcurrency', workerData.hardwareConcurrency),
|
||||||
|
getMaxTouchPoints('maxTouchPoints'),
|
||||||
|
getPlatform('platform', workerData.platform),
|
||||||
|
getUserAgent('userAgent', workerData.userAgent),
|
||||||
|
getAppVersion('appVersion', workerData.appVersion),
|
||||||
|
getLanguage('language', workerData.language),
|
||||||
|
getLanguages('languages', workerData.languages),
|
||||||
|
getCookieEnabled('cookieEnabled'),
|
||||||
|
getDoNotTrack('doNotTrack'),
|
||||||
|
getWebDriver('webdriver'),
|
||||||
|
getPlugins('plugins'),
|
||||||
|
getVendor('vendor'),
|
||||||
|
];
|
||||||
|
|
||||||
|
export default getNavigator;
|
||||||
77
frontend/src/utils/other.js
Normal file
77
frontend/src/utils/other.js
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
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;
|
||||||
137
frontend/src/utils/screen.js
Normal file
137
frontend/src/utils/screen.js
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
const getWidth = (key) => ({
|
||||||
|
key: 'Width',
|
||||||
|
code: `window.screen.${key}`,
|
||||||
|
value: window.screen[key],
|
||||||
|
issues: [
|
||||||
|
checkScreenProperties('width'),
|
||||||
|
checkScreenValue('width'),
|
||||||
|
checkScreenPrototype('width'),
|
||||||
|
checkWidth(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAvailWidth = (key) => ({
|
||||||
|
key: 'Avail width',
|
||||||
|
code: `window.screen.${key}`,
|
||||||
|
value: window.screen[key],
|
||||||
|
issues: [
|
||||||
|
checkScreenProperties('availWidth'),
|
||||||
|
checkScreenValue('availWidth'),
|
||||||
|
checkScreenPrototype('availWidth'),
|
||||||
|
checkWidth(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getOuterWidth = (key) => ({
|
||||||
|
key: 'Outer width',
|
||||||
|
code: `window.${key}`,
|
||||||
|
value: window[key],
|
||||||
|
issues: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getHeight = (key) => ({
|
||||||
|
key: 'Height',
|
||||||
|
code: `window.screen.${key}`,
|
||||||
|
value: window.screen[key],
|
||||||
|
issues: [
|
||||||
|
checkScreenProperties('height'),
|
||||||
|
checkScreenValue('height'),
|
||||||
|
checkScreenPrototype('height'),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAvailHeight = (key) => ({
|
||||||
|
key: 'Avail height',
|
||||||
|
code: `window.screen.${key}`,
|
||||||
|
value: window.screen[key],
|
||||||
|
issues: [
|
||||||
|
checkScreenProperties('availHeight'),
|
||||||
|
checkScreenValue('availHeight'),
|
||||||
|
checkScreenPrototype('availHeight'),
|
||||||
|
checkHeight(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getOuterHeight = (key) => ({
|
||||||
|
key: 'Outer height',
|
||||||
|
code: `window.${key}`,
|
||||||
|
value: window[key],
|
||||||
|
issues: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getPixelDepth = (key) => ({
|
||||||
|
key: 'Pixel depth',
|
||||||
|
code: `window.screen.${key}`,
|
||||||
|
value: window.screen[key],
|
||||||
|
issues: [
|
||||||
|
checkScreenProperties('pixelDepth'),
|
||||||
|
checkScreenValue('pixelDepth'),
|
||||||
|
checkScreenPrototype('pixelDepth'),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getColorDepth = (key) => ({
|
||||||
|
key: 'Color depth',
|
||||||
|
code: `window.screen.${key}`,
|
||||||
|
value: window.screen[key],
|
||||||
|
issues: [
|
||||||
|
checkScreenProperties('colorDepth'),
|
||||||
|
checkScreenValue('colorDepth'),
|
||||||
|
checkScreenPrototype('colorDepth'),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkScreenValue = (key) => {
|
||||||
|
if (
|
||||||
|
Object.getOwnPropertyDescriptor(Screen.prototype, key).value !== undefined
|
||||||
|
) {
|
||||||
|
return 'Failed descriptor.value undefined';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkScreenPrototype = (key) => {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const check = Screen.prototype[key];
|
||||||
|
return 'Failed Navigator.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 wider then width';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkHeight = () => {
|
||||||
|
if (window.screen.availHeight > window.screen.height) {
|
||||||
|
return 'Avail height is wider then height';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkScreenProperties = (key) => {
|
||||||
|
if (Object.getOwnPropertyDescriptor(window.screen, key) !== undefined) {
|
||||||
|
return 'Failed undefined properties';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getScreen = () => [
|
||||||
|
getWidth('width'),
|
||||||
|
getAvailWidth('availWidth'),
|
||||||
|
getOuterWidth('outerWidth'),
|
||||||
|
getHeight('height'),
|
||||||
|
getAvailHeight('availHeight'),
|
||||||
|
getOuterHeight('outerHeight'),
|
||||||
|
getPixelDepth('pixelDepth'),
|
||||||
|
getColorDepth('colorDepth'),
|
||||||
|
];
|
||||||
|
|
||||||
|
export default getScreen;
|
||||||
49
frontend/src/utils/userAgent.js
Normal file
49
frontend/src/utils/userAgent.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
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;
|
||||||
|
|
@ -4046,6 +4046,15 @@ dom-serializer@0:
|
||||||
domelementtype "^2.0.1"
|
domelementtype "^2.0.1"
|
||||||
entities "^2.0.0"
|
entities "^2.0.0"
|
||||||
|
|
||||||
|
dom-serializer@^1.0.1:
|
||||||
|
version "1.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91"
|
||||||
|
integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.0.1"
|
||||||
|
domhandler "^4.2.0"
|
||||||
|
entities "^2.0.0"
|
||||||
|
|
||||||
domain-browser@^1.1.1:
|
domain-browser@^1.1.1:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||||
|
|
@ -4061,6 +4070,11 @@ domelementtype@^2.0.1:
|
||||||
resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz"
|
resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz"
|
||||||
integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==
|
integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==
|
||||||
|
|
||||||
|
domelementtype@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
|
||||||
|
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
|
||||||
|
|
||||||
domexception@^2.0.1:
|
domexception@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz"
|
||||||
|
|
@ -4068,6 +4082,13 @@ domexception@^2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
webidl-conversions "^5.0.0"
|
webidl-conversions "^5.0.0"
|
||||||
|
|
||||||
|
domhandler@4.2.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059"
|
||||||
|
integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.2.0"
|
||||||
|
|
||||||
domhandler@^2.3.0:
|
domhandler@^2.3.0:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
resolved "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz"
|
resolved "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz"
|
||||||
|
|
@ -4075,6 +4096,13 @@ domhandler@^2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "1"
|
domelementtype "1"
|
||||||
|
|
||||||
|
domhandler@^4.0.0, domhandler@^4.2.0:
|
||||||
|
version "4.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f"
|
||||||
|
integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.2.0"
|
||||||
|
|
||||||
domutils@^1.5.1, domutils@^1.7.0:
|
domutils@^1.5.1, domutils@^1.7.0:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz"
|
resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz"
|
||||||
|
|
@ -4083,6 +4111,15 @@ domutils@^1.5.1, domutils@^1.7.0:
|
||||||
dom-serializer "0"
|
dom-serializer "0"
|
||||||
domelementtype "1"
|
domelementtype "1"
|
||||||
|
|
||||||
|
domutils@^2.5.2:
|
||||||
|
version "2.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
||||||
|
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
|
||||||
|
dependencies:
|
||||||
|
dom-serializer "^1.0.1"
|
||||||
|
domelementtype "^2.2.0"
|
||||||
|
domhandler "^4.2.0"
|
||||||
|
|
||||||
dot-case@^3.0.4:
|
dot-case@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz"
|
resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz"
|
||||||
|
|
@ -4677,6 +4714,11 @@ execa@^4.0.0:
|
||||||
signal-exit "^3.0.2"
|
signal-exit "^3.0.2"
|
||||||
strip-final-newline "^2.0.0"
|
strip-final-newline "^2.0.0"
|
||||||
|
|
||||||
|
exenv@^1.2.0:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
|
||||||
|
integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
|
||||||
|
|
||||||
exit@^0.1.2:
|
exit@^0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
|
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
|
||||||
|
|
@ -5163,6 +5205,11 @@ getpass@^0.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
assert-plus "^1.0.0"
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
|
github-buttons@^2.8.0:
|
||||||
|
version "2.19.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/github-buttons/-/github-buttons-2.19.1.tgz#9703755a4da1c5b229d3f1d875c2f787efa59bd5"
|
||||||
|
integrity sha512-us6ZC0bFYLfBq2CkZJJRpdPP5JlB6+kWFTdw8iK3E7yoMKdoLhDkqQHelJ+39UVR2zQbfXN5gNt3cVYp4fAuXA==
|
||||||
|
|
||||||
glob-parent@^3.1.0:
|
glob-parent@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz"
|
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz"
|
||||||
|
|
@ -5423,6 +5470,14 @@ html-comment-regex@^1.1.0:
|
||||||
resolved "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz"
|
resolved "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz"
|
||||||
integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
|
integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
|
||||||
|
|
||||||
|
html-dom-parser@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-1.0.1.tgz#5d147fed6656c12918edbcea4a423eefe8d0e715"
|
||||||
|
integrity sha512-uKXISKlHzB/l9A08jrs2wseQJ9b864ZfEdmIZskj10cuP6HxCOMHSK0RdluV8NVQaWs0PwefN7d8wqG3jR0IbQ==
|
||||||
|
dependencies:
|
||||||
|
domhandler "4.2.0"
|
||||||
|
htmlparser2 "6.1.0"
|
||||||
|
|
||||||
html-encoding-sniffer@^2.0.1:
|
html-encoding-sniffer@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz"
|
||||||
|
|
@ -5453,6 +5508,16 @@ html-minifier-terser@^5.0.1:
|
||||||
relateurl "^0.2.7"
|
relateurl "^0.2.7"
|
||||||
terser "^4.6.3"
|
terser "^4.6.3"
|
||||||
|
|
||||||
|
html-react-parser@^1.2.8:
|
||||||
|
version "1.2.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-1.2.8.tgz#6806984a5056417de38fc4b52145bd56b1b32d73"
|
||||||
|
integrity sha512-fPPbnMNbVuceyJARZTCu2/Ai7XbVsfncwVUl3IFpgV8BG7lv6hm+Z8NWXRgc2r1DXTf1wRnLaPDL4unbln/r+g==
|
||||||
|
dependencies:
|
||||||
|
domhandler "4.2.0"
|
||||||
|
html-dom-parser "1.0.1"
|
||||||
|
react-property "1.0.1"
|
||||||
|
style-to-js "1.1.0"
|
||||||
|
|
||||||
html-webpack-plugin@4.5.0:
|
html-webpack-plugin@4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz"
|
resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz"
|
||||||
|
|
@ -5468,6 +5533,16 @@ html-webpack-plugin@4.5.0:
|
||||||
tapable "^1.1.3"
|
tapable "^1.1.3"
|
||||||
util.promisify "1.0.0"
|
util.promisify "1.0.0"
|
||||||
|
|
||||||
|
htmlparser2@6.1.0:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
|
||||||
|
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.0.1"
|
||||||
|
domhandler "^4.0.0"
|
||||||
|
domutils "^2.5.2"
|
||||||
|
entities "^2.0.0"
|
||||||
|
|
||||||
htmlparser2@^3.10.1:
|
htmlparser2@^3.10.1:
|
||||||
version "3.10.1"
|
version "3.10.1"
|
||||||
resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz"
|
resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz"
|
||||||
|
|
@ -5700,6 +5775,11 @@ ini@^1.3.5:
|
||||||
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
|
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
|
||||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||||
|
|
||||||
|
inline-style-parser@0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
|
||||||
|
integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
|
||||||
|
|
||||||
internal-ip@^4.3.0:
|
internal-ip@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz"
|
resolved "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz"
|
||||||
|
|
@ -6936,7 +7016,7 @@ loglevel@^1.6.8:
|
||||||
resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz"
|
resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz"
|
||||||
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
|
integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
|
||||||
|
|
||||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
||||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||||
|
|
@ -7889,11 +7969,6 @@ path-type@^4.0.0:
|
||||||
resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
|
||||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||||
|
|
||||||
pathseg@^1.2.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.npmjs.org/pathseg/-/pathseg-1.2.0.tgz"
|
|
||||||
integrity sha512-+pQS7lTaoVIXhaCW7R3Wd/165APzZHWzYVqe7dxzdupxQwebgpBaCmf0/XZwmoA/rkDq3qvzO0qv4d5oFVrBRw==
|
|
||||||
|
|
||||||
pbkdf2@^3.0.3:
|
pbkdf2@^3.0.3:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
|
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
|
||||||
|
|
@ -8969,6 +9044,13 @@ react-error-overlay@^6.0.9:
|
||||||
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz"
|
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz"
|
||||||
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
||||||
|
|
||||||
|
react-github-btn@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-github-btn/-/react-github-btn-1.2.1.tgz#ece93f609a4bad5e7eb9f47ae49bfaba69466dce"
|
||||||
|
integrity sha512-/gXD01mHAOhW0xYuNJFDn08OGjaMXOjcg6GCKVPdHvQcWzswH4aT85DLDAAJ6Zhw/71veSIH4Kx1BTBfy69SsA==
|
||||||
|
dependencies:
|
||||||
|
github-buttons "^2.8.0"
|
||||||
|
|
||||||
react-is@^16.8.1:
|
react-is@^16.8.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||||
|
|
@ -8979,6 +9061,26 @@ react-is@^17.0.1:
|
||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz"
|
||||||
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
|
||||||
|
|
||||||
|
react-lifecycles-compat@^3.0.0:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
|
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||||
|
|
||||||
|
react-modal@^3.14.3:
|
||||||
|
version "3.14.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.14.3.tgz#7eb7c5ec85523e5843e2d4737cc17fc3f6aeb1c0"
|
||||||
|
integrity sha512-+C2KODVKyu20zHXPJxfOOcf571L1u/EpFlH+oS/3YDn8rgVE51QZuxuuIwabJ8ZFnOEHaD+r6XNjqwtxZnXO0g==
|
||||||
|
dependencies:
|
||||||
|
exenv "^1.2.0"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-lifecycles-compat "^3.0.0"
|
||||||
|
warning "^4.0.3"
|
||||||
|
|
||||||
|
react-property@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-property/-/react-property-1.0.1.tgz#4ae4211557d0a0ae050a71aa8ad288c074bea4e6"
|
||||||
|
integrity sha512-1tKOwxFn3dXVomH6pM9IkLkq2Y8oh+fh/lYW3MJ/B03URswUTqttgckOlbxY2XHF3vPG6uanSc4dVsLW/wk3wQ==
|
||||||
|
|
||||||
react-refresh@^0.8.3:
|
react-refresh@^0.8.3:
|
||||||
version "0.8.3"
|
version "0.8.3"
|
||||||
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz"
|
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz"
|
||||||
|
|
@ -9050,14 +9152,10 @@ react-scripts@4.0.3:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "^2.1.3"
|
fsevents "^2.1.3"
|
||||||
|
|
||||||
react-tsparticles@^1.28.0:
|
react-webworker@^2.1.0:
|
||||||
version "1.28.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.npmjs.org/react-tsparticles/-/react-tsparticles-1.28.0.tgz"
|
resolved "https://registry.yarnpkg.com/react-webworker/-/react-webworker-2.1.0.tgz#6bfe5d74d3f3e5bae89f316b8760b3e8cc551420"
|
||||||
integrity sha512-3GSvD/1sHlpyhpzdWcP/uSy5AeoEKr+9ZZjaHGubCHualpSu2UilNunpw49J5AQq6AzsP4WFGI9bVulj3Uw0iw==
|
integrity sha512-Rl0LeJa7zAQHAZ5KpyWQ/7aHcdy6AKJcJI7UIGlFo2e2sJw7u87hg4ck1cWb70DO0PrW0NwNZrMCgTi23g09JQ==
|
||||||
dependencies:
|
|
||||||
fast-deep-equal "^3.1.3"
|
|
||||||
tslib "^2.2.0"
|
|
||||||
tsparticles "^1.28.0"
|
|
||||||
|
|
||||||
react@^17.0.2:
|
react@^17.0.2:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
|
|
@ -10237,6 +10335,20 @@ style-loader@1.3.0:
|
||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
schema-utils "^2.7.0"
|
schema-utils "^2.7.0"
|
||||||
|
|
||||||
|
style-to-js@1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.0.tgz#631cbb20fce204019b3aa1fcb5b69d951ceac4ac"
|
||||||
|
integrity sha512-1OqefPDxGrlMwcbfpsTVRyzwdhr4W0uxYQzeA2F1CBc8WG04udg2+ybRnvh3XYL4TdHQrCahLtax2jc8xaE6rA==
|
||||||
|
dependencies:
|
||||||
|
style-to-object "0.3.0"
|
||||||
|
|
||||||
|
style-to-object@0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46"
|
||||||
|
integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==
|
||||||
|
dependencies:
|
||||||
|
inline-style-parser "0.1.1"
|
||||||
|
|
||||||
stylehacks@^4.0.0:
|
stylehacks@^4.0.0:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz"
|
resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz"
|
||||||
|
|
@ -10553,13 +10665,6 @@ tslib@^2.0.3, tslib@^2.2.0:
|
||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz"
|
||||||
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
||||||
|
|
||||||
tsparticles@^1.28.0:
|
|
||||||
version "1.28.0"
|
|
||||||
resolved "https://registry.npmjs.org/tsparticles/-/tsparticles-1.28.0.tgz"
|
|
||||||
integrity sha512-JDKNxuG99J64vF6cTfQ3S+lnhwK+LKHJYn46j9SX2Mae0gog34T0fm6cM0b1CRkB/MeCJ3mF0KaLRh5OdSEV1Q==
|
|
||||||
optionalDependencies:
|
|
||||||
pathseg "^1.2.0"
|
|
||||||
|
|
||||||
tsutils@^3.17.1:
|
tsutils@^3.17.1:
|
||||||
version "3.20.0"
|
version "3.20.0"
|
||||||
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz"
|
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz"
|
||||||
|
|
@ -10921,6 +11026,13 @@ walker@^1.0.7, walker@~1.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
makeerror "1.0.x"
|
makeerror "1.0.x"
|
||||||
|
|
||||||
|
warning@^4.0.3:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||||
|
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
watchpack-chokidar2@^2.0.1:
|
watchpack-chokidar2@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957"
|
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue