@@ -32,24 +35,25 @@ const FingerprintBlock = () => {
>
)}
Success! Re-scan browser.
+
);
};
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 (
-
+
);
};
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 = () => (
-
);
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 (
-
- );
-};
-
-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 }) => (
+
+);
+
+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 (
-
+);
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 = () => (
+ <>
+
- );
-};
+ >
+);
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 }) => (
+
+);
+
+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 (
+
+ );
+};
+
+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 }) => (
-
-);
-
-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 = () => (
+
+);
+
+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 = () => (
-
-);
-
-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 }) => (
-
-);
-
-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.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"