diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index d2bcd7e..6ab6eda 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -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', }, }; diff --git a/frontend/package.json b/frontend/package.json index b528610..acf0bcc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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": { diff --git a/frontend/public/index.html b/frontend/public/index.html index 4c641ef..3a882cf 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -22,8 +22,23 @@ + + + + + + + + + Vytal diff --git a/frontend/public/worker.js b/frontend/public/worker.js new file mode 100644 index 0000000..e988ea8 --- /dev/null +++ b/frontend/public/worker.js @@ -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); diff --git a/frontend/src/components/App.css b/frontend/src/components/App.css new file mode 100644 index 0000000..7f3dacf --- /dev/null +++ b/frontend/src/components/App.css @@ -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; + } +} diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js index 41c43de..956339a 100644 --- a/frontend/src/components/App.js +++ b/frontend/src/components/App.js @@ -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 = () => (
- +
); diff --git a/frontend/src/components/Block.js b/frontend/src/components/Block.js new file mode 100644 index 0000000..1e35fa7 --- /dev/null +++ b/frontend/src/components/Block.js @@ -0,0 +1,5 @@ +const ContentBlock = ({ children }) => ( +
{children}
+); + +export default ContentBlock; diff --git a/frontend/src/components/Blocks.css b/frontend/src/components/Blocks.css new file mode 100644 index 0000000..9bd94c4 --- /dev/null +++ b/frontend/src/components/Blocks.css @@ -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; + } +} diff --git a/frontend/src/components/Blocks.js b/frontend/src/components/Blocks.js new file mode 100644 index 0000000..dea9e15 --- /dev/null +++ b/frontend/src/components/Blocks.js @@ -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 ? ( + <> +
+ +
+
+ +
+
+ + +
+ + ) : ( +
+
Loading...
+
+ )} + + ); +}; + +export default Blocks; diff --git a/frontend/src/components/BlocksOne.js b/frontend/src/components/BlocksOne.js new file mode 100644 index 0000000..638098f --- /dev/null +++ b/frontend/src/components/BlocksOne.js @@ -0,0 +1,15 @@ +import UserAgentBlock from './UserAgentBlock'; +import IntlBlock from './IntlBlock'; +import NavigatorBlock from './NavigatorBlock'; +import FingerprintBlock from './FingerprintBlock'; + +const BlocksOne = ({ workerData }) => ( + <> + + + + + +); + +export default BlocksOne; diff --git a/frontend/src/components/BlocksTwo.js b/frontend/src/components/BlocksTwo.js new file mode 100644 index 0000000..abbe95b --- /dev/null +++ b/frontend/src/components/BlocksTwo.js @@ -0,0 +1,15 @@ +import OtherBlock from './OtherBlock'; +import ScreenBlock from './ScreenBlock'; +import LocationBlock from './LocationBlock'; +import ConnectionBlock from './ConnectionBlock'; + +const BlocksTwo = ({ workerData, connectionData }) => ( + <> + + + + + +); + +export default BlocksTwo; diff --git a/frontend/src/components/ConnectionBlock.js b/frontend/src/components/ConnectionBlock.js index cf22cbd..c138cdc 100644 --- a/frontend/src/components/ConnectionBlock.js +++ b/frontend/src/components/ConnectionBlock.js @@ -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 }) => ( + +

Connection

+ +

+ Explanation: Your IP address reveals information about your + connection.{' '} + + Read more + +

+ +); - useEffect(() => { - fetchAPI(setData, setDisplay); - }, []); - - return ( - -

Connection

- {display === 1 &&
} - {display === 0 && ( -
- Unable to fetch info. Adblock or content filter may have prevented - data from loading. -
- )} -

- Explanation: Your IP address reveals information about your - connection. Using a VPN or Tor will hide your connection info. -

- - ); -}; - -export default ConnectionBlock; +export default LocationBlock; diff --git a/frontend/src/components/ContentList.js b/frontend/src/components/ContentList.js deleted file mode 100644 index 5af1a5b..0000000 --- a/frontend/src/components/ContentList.js +++ /dev/null @@ -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: , - browser: , - fingerprint: , -}; - -const ContentList = ({ items }) => ( -
- {items.map((item) => ( -
-
{Icons[item.icon]}
-
-

{item.title}

-
{item.body}
-
-
- ))} -
-); - -export default ContentList; diff --git a/frontend/src/components/FiltersBlock.js b/frontend/src/components/FiltersBlock.js deleted file mode 100644 index 03e7e1f..0000000 --- a/frontend/src/components/FiltersBlock.js +++ /dev/null @@ -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 ( - -

Content Filters

-
-

- Explanation: Although content filters like adblock can protect - you against certain methods of tracking, it can also be used as another - identification metric. -

- - ); -}; - -export default FiltersBlock; diff --git a/frontend/src/components/FingerprintBlock.css b/frontend/src/components/FingerprintBlock.css new file mode 100644 index 0000000..fc22102 --- /dev/null +++ b/frontend/src/components/FingerprintBlock.css @@ -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); + } +} diff --git a/frontend/src/components/FingerprintBlock.js b/frontend/src/components/FingerprintBlock.js index bdb2327..afe2f17 100644 --- a/frontend/src/components/FingerprintBlock.js +++ b/frontend/src/components/FingerprintBlock.js @@ -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 ( - +

Fingerprint

{load && ( <> - {name ? ( -
+ {signature !== undefined ? ( +
+
+ ) : (
{hash}
@@ -32,24 +35,25 @@ const FingerprintBlock = () => { )}

- Explanation: 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. + Explanation: 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.

- {saved ? ( -

Success! Re-scan browser.

- ) : ( -
{ - handleSave(e, hash, setSaved); - }} - > - - - - )} - +
postSignature(hash, e.target[0].value)}> + + + + ); }; diff --git a/frontend/src/components/FontsBlock.js b/frontend/src/components/FontsBlock.js index 1c85c35..ada90a2 100644 --- a/frontend/src/components/FontsBlock.js +++ b/frontend/src/components/FontsBlock.js @@ -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 ( - +

System Fonts

@@ -54,7 +54,7 @@ const FontsBlock = () => { fonts that you have installed that aren't common for your system can make you easily identifiable.

- + ); }; diff --git a/frontend/src/components/GitHub.css b/frontend/src/components/GitHub.css new file mode 100644 index 0000000..2c47124 --- /dev/null +++ b/frontend/src/components/GitHub.css @@ -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; + } +} diff --git a/frontend/src/components/Github.js b/frontend/src/components/Github.js index cc521b7..04dcdf8 100644 --- a/frontend/src/components/Github.js +++ b/frontend/src/components/Github.js @@ -1,15 +1,16 @@ -import { ReactComponent as GithubImg } from '../images/github.svg'; +import './GitHub.css'; +import GitHubButton from 'react-github-btn'; const Github = () => ( - - - +
+ + Star + +
); export default Github; diff --git a/frontend/src/components/HardwareBlock.js b/frontend/src/components/HardwareBlock.js deleted file mode 100644 index dce3ee0..0000000 --- a/frontend/src/components/HardwareBlock.js +++ /dev/null @@ -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 ( - -

Hardware

-
-

- Explanation: JavaScript can be used to find information about - your hardware. This information can be used to create a fingerprint. -

- - ); -}; - -export default HardwareBlock; diff --git a/frontend/src/components/IntlBlock.js b/frontend/src/components/IntlBlock.js new file mode 100644 index 0000000..062c5df --- /dev/null +++ b/frontend/src/components/IntlBlock.js @@ -0,0 +1,24 @@ +import Block from './Block'; +import Table from './Table'; +import getIntl from '../utils/intl'; + +const IntlBlock = ({ workerData }) => ( + +

Intl

+
+

+ Explanation: The Intl object exposes info about your computer.{' '} + + Read more + +

+ +); + +export default IntlBlock; diff --git a/frontend/src/components/LocationBlock.js b/frontend/src/components/LocationBlock.js index 5768911..6e7ef87 100644 --- a/frontend/src/components/LocationBlock.js +++ b/frontend/src/components/LocationBlock.js @@ -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 ( - -

Location

- {display === 1 && ( - <> - Map of current location -
- - )} - {display === 0 && ( -
- Unable to fetch info. Adblock or content filter may have prevented - data from loading. -
- )} -

- Explanation: Your IP address can be used to determine your - location. Using a VPN or Tor will hide your true location. -

- - ); -}; +const LocationBlock = ({ connectionData, workerData }) => ( + +

Location

+ Map of current location +
+

+ Explanation: Your IP address can be used to determine your + location.{' '} + + Read more + +

+ +); export default LocationBlock; diff --git a/frontend/src/components/Logo.css b/frontend/src/components/Logo.css new file mode 100644 index 0000000..23bebe7 --- /dev/null +++ b/frontend/src/components/Logo.css @@ -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; + } +} diff --git a/frontend/src/components/Logo.js b/frontend/src/components/Logo.js index d833a1d..c8f4a19 100644 --- a/frontend/src/components/Logo.js +++ b/frontend/src/components/Logo.js @@ -1,3 +1,4 @@ +import './Logo.css'; import { ReactComponent as LogoImg } from '../images/logo.svg'; const Logo = () => ( diff --git a/frontend/src/components/MainColumn.css b/frontend/src/components/MainColumn.css new file mode 100644 index 0000000..9c71520 --- /dev/null +++ b/frontend/src/components/MainColumn.css @@ -0,0 +1,5 @@ +.centerBlockOuter { + display: flex; + justify-content: center; + gap: 24px; +} diff --git a/frontend/src/components/MainColumn.js b/frontend/src/components/MainColumn.js index d06c105..d652664 100644 --- a/frontend/src/components/MainColumn.js +++ b/frontend/src/components/MainColumn.js @@ -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 = () => ( + <> +
-
- - {scan ? : } -
+
- ); -}; + +); export default MainColumn; diff --git a/frontend/src/components/NavigatorBlock.js b/frontend/src/components/NavigatorBlock.js new file mode 100644 index 0000000..5a0841c --- /dev/null +++ b/frontend/src/components/NavigatorBlock.js @@ -0,0 +1,25 @@ +import Block from './Block'; +import Table from './Table'; +import getNavigator from '../utils/navigator'; + +const NavigatorBlock = ({ workerData }) => ( + +

Navigator

+
+

+ Explanation: The Navigator interface exposes info about your + computer.{' '} + + Read more + +

+ +); + +export default NavigatorBlock; diff --git a/frontend/src/components/OtherBlock.js b/frontend/src/components/OtherBlock.js new file mode 100644 index 0000000..3f269f3 --- /dev/null +++ b/frontend/src/components/OtherBlock.js @@ -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 ( + +

Other

+ {battery && adBlock !== undefined && ( +
+ )} + + ); +}; + +export default OtherBlock; diff --git a/frontend/src/components/ScanBlock.js b/frontend/src/components/ScanBlock.js deleted file mode 100644 index dc43139..0000000 --- a/frontend/src/components/ScanBlock.js +++ /dev/null @@ -1,9 +0,0 @@ -const ContentBlock = ({ children }) => ( -
-
-
{children}
-
-
-); - -export default ContentBlock; diff --git a/frontend/src/components/ScanBlocks.js b/frontend/src/components/ScanBlocks.js deleted file mode 100644 index b9ae1e3..0000000 --- a/frontend/src/components/ScanBlocks.js +++ /dev/null @@ -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 = () => ( - <> - - - - - - - {/* */} - -); - -export default ScanBlocks; diff --git a/frontend/src/components/ScreenBlock.js b/frontend/src/components/ScreenBlock.js new file mode 100644 index 0000000..c2c4132 --- /dev/null +++ b/frontend/src/components/ScreenBlock.js @@ -0,0 +1,24 @@ +import Block from './Block'; +import Table from './Table'; +import getScreen from '../utils/screen'; + +const ScreenBlock = () => ( + +

Screen

+
+

+ Explanation: The Screen interface exposes info about your computer.{' '} + + Read more + +

+ +); + +export default ScreenBlock; diff --git a/frontend/src/components/SoftwareBlock.js b/frontend/src/components/SoftwareBlock.js deleted file mode 100644 index 1a678d7..0000000 --- a/frontend/src/components/SoftwareBlock.js +++ /dev/null @@ -1,16 +0,0 @@ -import ScanBlock from './ScanBlock'; -import Table from './Table'; -import { getSoftware } from './main'; - -const SoftwareBlock = () => ( - -

Software

-
-

- Explanation: JavaScript can be used to find information about your - software. This information can be used to create a fingerprint. -

- -); - -export default SoftwareBlock; diff --git a/frontend/src/components/StartBlock.js b/frontend/src/components/StartBlock.js deleted file mode 100644 index 08db904..0000000 --- a/frontend/src/components/StartBlock.js +++ /dev/null @@ -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 }) => ( - -

About

-
- Vytal shows you what traces your browser leaves behind while surfing the - web. This scan allows you to understand how easy it is to identify and - track your browser even while using private mode. -
- - setScan(true)} - id="scanButton" - value="Scan Browser" - /> -
-); - -export default StartBlock; diff --git a/frontend/src/components/Table.css b/frontend/src/components/Table.css new file mode 100644 index 0000000..9af2b4a --- /dev/null +++ b/frontend/src/components/Table.css @@ -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); +} diff --git a/frontend/src/components/Table.js b/frontend/src/components/Table.js index cb8c3b4..ab50f27 100644 --- a/frontend/src/components/Table.js +++ b/frontend/src/components/Table.js @@ -1,12 +1,12 @@ +import './Table.css'; +import TableRow from './TableRow'; + const Table = ({ data }) => (
{data.map((item) => ( - - - - - + + ))}
{item.title}{item.value}
diff --git a/frontend/src/components/TableRow.css b/frontend/src/components/TableRow.css new file mode 100644 index 0000000..839ad9f --- /dev/null +++ b/frontend/src/components/TableRow.css @@ -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; + } +} diff --git a/frontend/src/components/TableRow.js b/frontend/src/components/TableRow.js new file mode 100644 index 0000000..b0e4464 --- /dev/null +++ b/frontend/src/components/TableRow.js @@ -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 ( + <> + + {item.key} + {item.value || 'N/A'} + + {issues ? ( + + ) : ( + + )} + + + +
+
{item.key} issues
+ +
+
    + {item.issues.filter(Boolean).map((ele, index) => ( +
  • {ele}
  • + ))} +
+
+ + ); +}; + +export default TableRow; diff --git a/frontend/src/components/UserAgentBlock.js b/frontend/src/components/UserAgentBlock.js new file mode 100644 index 0000000..7437262 --- /dev/null +++ b/frontend/src/components/UserAgentBlock.js @@ -0,0 +1,25 @@ +import Block from './Block'; +import Table from './Table'; +import getUserAgent from '../utils/userAgent'; + +const UserAgentBlock = ({ workerAgent }) => ( + +

User Agent

+ +

+ Explanation: Your user agent can be parsed to determine information + about your browser or operating system.{' '} + + Read more + +

+ +); + +export default UserAgentBlock; diff --git a/frontend/src/components/main.js b/frontend/src/components/main.js deleted file mode 100644 index aecbd9d..0000000 --- a/frontend/src/components/main.js +++ /dev/null @@ -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); -}; diff --git a/frontend/src/images/checkCircle.svg b/frontend/src/images/checkCircle.svg new file mode 100644 index 0000000..b958876 --- /dev/null +++ b/frontend/src/images/checkCircle.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/images/github.svg b/frontend/src/images/github.svg deleted file mode 100644 index bc1ce35..0000000 --- a/frontend/src/images/github.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/src/images/x.svg b/frontend/src/images/x.svg new file mode 100644 index 0000000..c528bcd --- /dev/null +++ b/frontend/src/images/x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/xCircle.svg b/frontend/src/images/xCircle.svg new file mode 100644 index 0000000..768426f --- /dev/null +++ b/frontend/src/images/xCircle.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/index.js b/frontend/src/index.js index 5831441..4a6f683 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -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'; diff --git a/frontend/src/particles.json b/frontend/src/particles.json deleted file mode 100644 index f79086e..0000000 --- a/frontend/src/particles.json +++ /dev/null @@ -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 -} diff --git a/frontend/src/styles/App.css b/frontend/src/styles/App.css deleted file mode 100644 index d194a2c..0000000 --- a/frontend/src/styles/App.css +++ /dev/null @@ -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); - } -} diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css deleted file mode 100644 index af471ba..0000000 --- a/frontend/src/styles/index.css +++ /dev/null @@ -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; -} diff --git a/frontend/src/utils/common.js b/frontend/src/utils/common.js new file mode 100644 index 0000000..a72cc3a --- /dev/null +++ b/frontend/src/utils/common.js @@ -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; +}; diff --git a/frontend/src/utils/connection.js b/frontend/src/utils/connection.js new file mode 100644 index 0000000..a86c03f --- /dev/null +++ b/frontend/src/utils/connection.js @@ -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], + }, + ]; +}; diff --git a/frontend/src/utils/fingerprint.js b/frontend/src/utils/fingerprint.js new file mode 100644 index 0000000..92bec38 --- /dev/null +++ b/frontend/src/utils/fingerprint.js @@ -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: [], + }, +]; diff --git a/frontend/src/utils/intl.js b/frontend/src/utils/intl.js new file mode 100644 index 0000000..1106741 --- /dev/null +++ b/frontend/src/utils/intl.js @@ -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; diff --git a/frontend/src/utils/navigator.js b/frontend/src/utils/navigator.js new file mode 100644 index 0000000..f22b3d1 --- /dev/null +++ b/frontend/src/utils/navigator.js @@ -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; diff --git a/frontend/src/utils/other.js b/frontend/src/utils/other.js new file mode 100644 index 0000000..627296e --- /dev/null +++ b/frontend/src/utils/other.js @@ -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; diff --git a/frontend/src/utils/screen.js b/frontend/src/utils/screen.js new file mode 100644 index 0000000..da3f8fa --- /dev/null +++ b/frontend/src/utils/screen.js @@ -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; diff --git a/frontend/src/utils/userAgent.js b/frontend/src/utils/userAgent.js new file mode 100644 index 0000000..7e2994d --- /dev/null +++ b/frontend/src/utils/userAgent.js @@ -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; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 015e27a..8b910e1 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -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"