Merge branch 'dev'

This commit is contained in:
z0ccc 2021-09-29 15:36:12 -04:00
commit eb2a8e2988
56 changed files with 1549 additions and 1034 deletions

View file

@ -38,5 +38,7 @@ module.exports = {
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'no-bitwise': 'off',
'react/no-array-index-key': 'off',
'dot-notation': 'off',
},
};

View file

@ -12,10 +12,13 @@
"bowser": "^2.11.0",
"crypto-js": "^4.0.0",
"emailjs-com": "^3.1.0",
"html-react-parser": "^1.2.8",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-github-btn": "^1.2.1",
"react-modal": "^3.14.3",
"react-scripts": "4.0.3",
"react-tsparticles": "^1.28.0",
"react-webworker": "^2.1.0",
"tslib": "^2.2.0"
},
"scripts": {

View file

@ -22,8 +22,23 @@
<meta name="theme-color" content="#000000" />
<meta
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" />
<title>Vytal</title>
</head>

14
frontend/public/worker.js Normal file
View 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);

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

View file

@ -1,13 +1,11 @@
import Particles from 'react-tsparticles';
import particlesOptions from '../particles.json';
import Github from './Github';
import MainColumn from './MainColumn';
import '../styles/App.css';
import './App.css';
const App = () => (
<div className="App">
<Github />
<Particles options={particlesOptions} />
<div className="background" />
<MainColumn />
</div>
);

View file

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

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

View 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;

View 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;

View 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;

View file

@ -1,32 +1,25 @@
import { useState, useEffect } from 'react';
import ScanBlock from './ScanBlock';
import Block from './Block';
import Table from './Table';
import { fetchAPI, getConnection } from './main';
import { getConnection } from '../utils/connection';
const ConnectionBlock = () => {
const [data, setData] = useState('');
const [display, setDisplay] = useState('');
const LocationBlock = ({ connectionData }) => (
<Block>
<h1>Connection</h1>
<Table data={getConnection(connectionData)} />
<p>
<b>Explanation:</b> Your IP address reveals information about your
connection.{' '}
<a
className="link"
target="_blank"
rel="noreferrer"
alt="Read more about ip connection"
href="https://en.wikipedia.org/wiki/IP_address"
>
Read more
</a>
</p>
</Block>
);
useEffect(() => {
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;
export default LocationBlock;

View file

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

View file

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

View 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);
}
}

View file

@ -1,29 +1,32 @@
import { useState } from 'react';
import ScanBlock from './ScanBlock';
import './FingerprintBlock.css';
import { useState, useEffect } from 'react';
import Block from './Block';
import Table from './Table';
import {
getHardware,
getWebGL,
getSoftware,
getFingerprint,
getSignature,
postSignature,
getHash,
getName,
handleSave,
} from './main';
getFingerprint,
} from '../utils/fingerprint';
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 (
<ScanBlock>
<Block>
<h1>Fingerprint</h1>
{load && (
<>
{name ? (
<Table data={getFingerprint(name, hash)} />
{signature !== undefined ? (
<div className="fingerprintTable">
<Table data={getFingerprint(signature, hash)} />
</div>
) : (
<div className="boxWrap">
<div className="hash">{hash}</div>
@ -32,24 +35,25 @@ const FingerprintBlock = () => {
</>
)}
<p>
<b>Explanation:</b> This is a unique identifier that can be used to
follow you around the web. Even if you clear cookies, change your IP or
use private mode the hash will stay the same. Enter your name below and
reload the page in private mode to test it out.
<b>Explanation:</b> This hash is calculated from your device data. Even
if you clear cookies, change your IP or use private mode the hash will
stay the same. Enter a signature and turn on a VPN to test it out.
</p>
{saved ? (
<p>Success! Re-scan browser.</p>
) : (
<form
onSubmit={(e) => {
handleSave(e, hash, setSaved);
}}
>
<input type="text" id="name" name="name" placeholder="Enter name" />
<input type="submit" id="saveButton" value="Save" maxLength="100" />
</form>
)}
</ScanBlock>
<form onSubmit={(e) => postSignature(hash, e.target[0].value)}>
<input
type="text"
id="signature"
name="signature"
placeholder="Enter signature"
/>
<input
type="submit"
className="saveButton"
value="Save"
maxLength="100"
/>
</form>
</Block>
);
};

View file

@ -1,5 +1,5 @@
import { useEffect } from 'react';
import ScanBlock from './ScanBlock';
import Block from './Block';
import fontList from '../fontList.json';
const FontsBlock = () => {
@ -45,7 +45,7 @@ const FontsBlock = () => {
}, []);
return (
<ScanBlock>
<Block>
<h1>System Fonts</h1>
<div className="fonts boxWrap" />
<p>
@ -54,7 +54,7 @@ const FontsBlock = () => {
fonts that you have installed that aren&apos;t common for your system
can make you easily identifiable.
</p>
</ScanBlock>
</Block>
);
};

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

View file

@ -1,15 +1,16 @@
import { ReactComponent as GithubImg } from '../images/github.svg';
import './GitHub.css';
import GitHubButton from 'react-github-btn';
const Github = () => (
<a
href="https://github.com/z0ccc/Vytal"
className="github"
target="_blank"
rel="noreferrer"
alt="Link to Github page"
>
<GithubImg />
</a>
<div className="gitHubButton">
<GitHubButton
href="https://github.com/z0ccc/Vytal"
data-color-scheme="no-preference: light; light: light; dark: light;"
aria-label="Star z0ccc/Vytal on GitHub"
>
Star
</GitHubButton>
</div>
);
export default Github;

View file

@ -1,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;

View 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;

View file

@ -1,37 +1,26 @@
import { useState, useEffect } from 'react';
import ScanBlock from './ScanBlock';
import Block from './Block';
import Table from './Table';
import { fetchAPI, getLocation, getMap } from './main';
import { getMap, getLocation } from '../utils/connection';
const LocationBlock = () => {
const [data, setData] = useState([]);
const [display, setDisplay] = useState('');
useEffect(() => {
fetchAPI(setData, setDisplay);
}, []);
return (
<ScanBlock>
<h1>Location</h1>
{display === 1 && (
<>
<img src={getMap(data)} alt="Map of current location" />
<Table data={getLocation(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 can be used to determine your
location. Using a VPN or Tor will hide your true location.
</p>
</ScanBlock>
);
};
const LocationBlock = ({ connectionData, workerData }) => (
<Block>
<h1>Location</h1>
<img src={getMap(connectionData)} alt="Map of current location" />
<Table data={getLocation(connectionData, workerData)} />
<p>
<b>Explanation:</b> Your IP address can be used to determine your
location.{' '}
<a
className="link"
target="_blank"
rel="noreferrer"
alt="Read more about ip location"
href="https://en.wikipedia.org/wiki/IP_address"
>
Read more
</a>
</p>
</Block>
);
export default LocationBlock;

View file

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

View file

@ -1,3 +1,4 @@
import './Logo.css';
import { ReactComponent as LogoImg } from '../images/logo.svg';
const Logo = () => (

View file

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

View file

@ -1,17 +1,13 @@
import { useState } from 'react';
import './MainColumn.css';
import Logo from './Logo';
import StartBlock from './StartBlock';
import ScanBlocks from './ScanBlocks';
import Blocks from './Blocks';
const MainColumn = () => {
const [scan, setScan] = useState(false);
return (
const MainColumn = () => (
<>
<Logo />
<div className="centerBlockOuter">
<div className="centerBlockInner">
<Logo />
{scan ? <ScanBlocks /> : <StartBlock scan={scan} setScan={setScan} />}
</div>
<Blocks />
</div>
);
};
</>
);
export default MainColumn;

View 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;

View 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;

View file

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

View file

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

View 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;

View file

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

View file

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

View 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);
}

View file

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

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

View 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;

View 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;

View file

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

View 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

View file

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

View 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

View 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

View file

@ -1,6 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './styles/index.css';
import App from './components/App';
import * as serviceWorker from './serviceWorker';

View file

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

View file

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

View file

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

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

View 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],
},
];
};

View 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: [],
},
];

View 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;

View 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;

View 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;

View 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;

View 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;

View file

@ -4046,6 +4046,15 @@ dom-serializer@0:
domelementtype "^2.0.1"
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:
version "1.2.0"
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"
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:
version "2.0.1"
resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz"
@ -4068,6 +4082,13 @@ domexception@^2.0.1:
dependencies:
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:
version "2.4.2"
resolved "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz"
@ -4075,6 +4096,13 @@ domhandler@^2.3.0:
dependencies:
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:
version "1.7.0"
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"
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:
version "3.0.4"
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"
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:
version "0.1.2"
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
@ -5163,6 +5205,11 @@ getpass@^0.1.1:
dependencies:
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:
version "3.1.0"
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"
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:
version "2.0.1"
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"
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:
version "4.5.0"
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"
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:
version "3.10.1"
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"
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:
version "4.3.0"
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"
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"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
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"
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:
version "3.1.2"
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"
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:
version "16.13.1"
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"
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:
version "0.8.3"
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz"
@ -9050,14 +9152,10 @@ react-scripts@4.0.3:
optionalDependencies:
fsevents "^2.1.3"
react-tsparticles@^1.28.0:
version "1.28.0"
resolved "https://registry.npmjs.org/react-tsparticles/-/react-tsparticles-1.28.0.tgz"
integrity sha512-3GSvD/1sHlpyhpzdWcP/uSy5AeoEKr+9ZZjaHGubCHualpSu2UilNunpw49J5AQq6AzsP4WFGI9bVulj3Uw0iw==
dependencies:
fast-deep-equal "^3.1.3"
tslib "^2.2.0"
tsparticles "^1.28.0"
react-webworker@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-webworker/-/react-webworker-2.1.0.tgz#6bfe5d74d3f3e5bae89f316b8760b3e8cc551420"
integrity sha512-Rl0LeJa7zAQHAZ5KpyWQ/7aHcdy6AKJcJI7UIGlFo2e2sJw7u87hg4ck1cWb70DO0PrW0NwNZrMCgTi23g09JQ==
react@^17.0.2:
version "17.0.2"
@ -10237,6 +10335,20 @@ style-loader@1.3.0:
loader-utils "^2.0.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:
version "4.0.3"
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"
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:
version "3.20.0"
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz"
@ -10921,6 +11026,13 @@ walker@^1.0.7, walker@~1.0.5:
dependencies:
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:
version "2.0.1"
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957"