Switching locatejs over for vytal

This commit is contained in:
z0ccc 2022-05-13 13:07:57 -04:00
parent 2db9eabf78
commit 143e646e4f
83 changed files with 8879 additions and 23137 deletions

View file

@ -1,11 +0,0 @@
{
"presets": [
// "@babel/preset-env"
"@babel/preset-react"
// "react-app"
],
"plugins": [
// "@babel/plugin-proposal-class-properties",
"react-hot-loader/babel"
]
}

View file

@ -1,6 +0,0 @@
{
"extends": "react-app",
"globals": {
"chrome": "readonly"
}
}

45
.eslintrc.js Normal file
View file

@ -0,0 +1,45 @@
module.exports = {
env: {
browser: true,
es2021: true,
webextensions: true,
},
extends: ['plugin:react/recommended', 'airbnb'],
plugins: ['react'],
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx'],
moduleDirectory: ['node_modules', 'src/'],
},
},
},
rules: {
'no-plusplus': 'off',
'comma-dangle': 'off',
'operator-linebreak': 'off',
'no-use-before-define': 'off',
'linebreak-style': 'off',
'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.js'] }],
'jsx-a11y/label-has-associated-control': 'off',
'one-var': 'off',
'one-var-declaration-per-line': 'off',
'object-curly-newline': 'off',
'implicit-arrow-linebreak': 'off',
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
},
],
'react/jsx-one-expression-per-line': 'off',
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'no-bitwise': 'off',
'react/no-array-index-key': 'off',
'nonblock-statement-body-position': 'off',
'react/button-has-type': 'off'
},
};

13
.gitignore vendored
View file

@ -1,7 +1,11 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
/images
App.test.js
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
@ -15,7 +19,8 @@
.env.development.local
.env.test.local
.env.production.local
.history
# secrets
secrets.*.js
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View file

@ -1,8 +0,0 @@
{
"singleQuote": true,
"trailingComma": "es5",
"requirePragma": false,
"arrowParens": "always",
"semi": false,
"editor.formatOnSave": true
}

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

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

View file

@ -1,3 +0,0 @@
{
"editor.formatOnSave": true
}

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
MIT License
Copyright (c) 2019 Michael Xieyang Liu
Copyright (c) 2022 z0ccc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

151
README.md Executable file → Normal file
View file

@ -1,151 +1,52 @@
<img src="src/assets/img/icon-128.png" width="64"/>
# LocateJS
# Chrome Extension Boilerplate with React 17 and Webpack 5
Check it out here: [https://locatejs.com/](https://locatejs.com/).
[![npm](https://img.shields.io/npm/v/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react)
[![npm-download](https://img.shields.io/npm/dw/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react)
[![npm](https://img.shields.io/npm/dm/chrome-extension-boilerplate-react)](https://www.npmjs.com/package/chrome-extension-boilerplate-react)
## About
[![dependencies Status](https://david-dm.org/lxieyang/chrome-extension-boilerplate-react/status.svg)](https://david-dm.org/lxieyang/chrome-extension-boilerplate-react)
[![devDependencies Status](https://david-dm.org/lxieyang/chrome-extension-boilerplate-react/dev-status.svg)](https://david-dm.org/lxieyang/chrome-extension-boilerplate-react?type=dev)
LocateJS predicts your location by analyzing your connection and system data. This scan allows you to understand how your location can be pinpointed even while using a VPN, location spoofer or private mode.
## Announcements
Accuracy of the location prediction is dependent on how well you have concealed your location data. The prediction can be anywhere from spot-on to inaccurate depending on how much identifiable and authentic info your exposing.
- **_This boilerplate adopts [Manifest V3](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/)!_** For V2 users, please check out the [manifest-v2](https://github.com/lxieyang/chrome-extension-boilerplate-react/tree/manifest-v2) branch, or use version [3.x](https://www.npmjs.com/package/chrome-extension-boilerplate-react/v/3.3.0).
- Check out the [Manifest V3 Migration Guide](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/).
- Recently added [devtools](https://developer.chrome.com/docs/extensions/mv3/devtools/) Support! Thanks [GeekaholicLin](https://github.com/lxieyang/chrome-extension-boilerplate-react/issues/17)!
- Recently updated from **[React](https://reactjs.org)** ~~16~~ to **17**!
- Recently updated from **[Webpack Dev Server](https://webpack.js.org/configuration/dev-server/)** ~~3.x~~ to **4.x** and **[Webpack](https://webpack.js.org/)** ~~4~~ to **5**!
- Recently added [TypeScript](https://www.typescriptlang.org/) Support!
## How to hide location
## Features
The best way to hide your location is to use [Tor](https://www.torproject.org/). Tor will completely hide both your connection and system data.
This is a basic Chrome Extensions boilerplate to help you write modular and modern Javascript code, load CSS easily and [automatic reload the browser on code changes](https://webpack.github.io/docs/webpack-dev-server.html#automatic-refresh).
As it's not always practical to use Tor (slow speeds, captcha loops, etc) the following are instructions on how to obscure your location without Tor.
This boilerplate is updated with:
To hide your connection data you will need to use a VPN or proxy
- [Chrome Extension Manifest V3](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/)
- [React 17](https://reactjs.org)
- [Webpack 5](https://webpack.js.org/)
- [Webpack Dev Server 4](https://webpack.js.org/configuration/dev-server/)
- [React Hot Loader](https://github.com/gaearon/react-hot-loader)
- [eslint-config-react-app](https://www.npmjs.com/package/eslint-config-react-app)
- [Prettier](https://prettier.io/)
- [TypeScript](https://www.typescriptlang.org/)
To hide your system data you will need to change your system settings. You can change your timezone to one with the same timezone offset so that the date on your computer stays the same.
This boilerplate is heavily inspired by and adapted from [https://github.com/samuelsimoes/chrome-extension-webpack-boilerplate](https://github.com/samuelsimoes/chrome-extension-webpack-boilerplate), with additional support for React 17 features, Webpack 5, and Webpack Dev Server 4.
Timezone/location spoofing extensions will not help as they cannot spoof data in [web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers).
Please open up an issue to nudge me to keep the npm packages up-to-date. FYI, it takes time to make different packages with different versions work together nicely.
You should select a language that has no country associated with it (en instead of en-US). You should remove all other languages.
## Installing and Running
Although adding multiple random languages will help obscure your location, it's not the best idea as it will give you a very unique [device fingerprint](https://en.wikipedia.org/wiki/Device_fingerprint).
### Procedures:
It's also a good idea to make sure your connection data matches your system data because if there are contradictions it would reveal that you are attempting to hide your location.
1. Check if your [Node.js](https://nodejs.org/) version is >= **14**.
2. Clone this repository.
3. Change the package's `name`, `description`, and `repository` fields in `package.json`.
4. Change the name of your extension on `src/manifest.json`.
5. Run `npm install` to install the dependencies.
6. Run `npm start`
7. Load your extension on Chrome following:
1. Access `chrome://extensions/`
2. Check `Developer mode`
3. Click on `Load unpacked extension`
4. Select the `build` folder.
8. Happy hacking.
## FAQ
## Structure
### Why did nothing change when Only use system data for prediction was selected?
All your extension's code must be placed in the `src` folder.
If the values do not change when this option is selected it means that the connection values were already removed from the prediction due to being detected as inaccurate.
The boilerplate is already prepared to have a popup, an options page, a background page, and a new tab page (which replaces the new tab page of your browser). But feel free to customize these.
### What do the green and red circles mean?
## TypeScript
A red circle indicates that the data in that row has been tampered with. If the true value cannot be derived then it is assumed to be inaccurate and will be left out of the location prediction. You can click on the red circle to see what types of issues have been detected.
This boilerplate now supports TypeScript! The `Options` Page is implemented using TypeScript. Please refer to `src/pages/Options/` for example usages.
A green circle indicates that no object tampering has been detected and that the value is legitimate.
## Webpack auto-reload and HRM
## Dev
To make your workflow much more efficient this boilerplate uses the [webpack server](https://webpack.github.io/docs/webpack-dev-server.html) to development (started with `npm start`) with auto reload feature that reloads the browser automatically every time that you save some file in your editor.
This application is built with Javascript and React.
You can run the dev mode on other port if you want. Just specify the env var `port` like this:
Clone this repo and run these commands to start the development server.
```
$ PORT=6002 npm run start
yarn
yarn run start
```
## Content Scripts
Although this boilerplate uses the webpack dev server, it's also prepared to write all your bundles files on the disk at every code change, so you can point, on your extension manifest, to your bundles that you want to use as [content scripts](https://developer.chrome.com/extensions/content_scripts), but you need to exclude these entry points from hot reloading [(why?)](https://github.com/samuelsimoes/chrome-extension-webpack-boilerplate/issues/4#issuecomment-261788690). To do so you need to expose which entry points are content scripts on the `webpack.config.js` using the `chromeExtensionBoilerplate -> notHotReload` config. Look the example below.
Let's say that you want use the `myContentScript` entry point as content script, so on your `webpack.config.js` you will configure the entry point and exclude it from hot reloading, like this:
```js
{
entry: {
myContentScript: "./src/js/myContentScript.js"
},
chromeExtensionBoilerplate: {
notHotReload: ["myContentScript"]
}
}
```
and on your `src/manifest.json`:
```json
{
"content_scripts": [
{
"matches": ["https://www.google.com/*"],
"js": ["myContentScript.bundle.js"]
}
]
}
```
## Intelligent Code Completion
Thanks to [@hudidit](https://github.com/lxieyang/chrome-extension-boilerplate-react/issues/4)'s kind suggestions, this boilerplate supports chrome-specific intelligent code completion using [@types/chrome](https://www.npmjs.com/package/@types/chrome).
## Packing
After the development of your extension run the command
```
$ NODE_ENV=production npm run build
```
Now, the content of `build` folder will be the extension ready to be submitted to the Chrome Web Store. Just take a look at the [official guide](https://developer.chrome.com/webstore/publish) to more infos about publishing.
## Secrets
If you are developing an extension that talks with some API you probably are using different keys for testing and production. Is a good practice you not commit your secret keys and expose to anyone that have access to the repository.
To this task this boilerplate import the file `./secrets.<THE-NODE_ENV>.js` on your modules through the module named as `secrets`, so you can do things like this:
_./secrets.development.js_
```js
export default { key: '123' };
```
_./src/popup.js_
```js
import secrets from 'secrets';
ApiCall({ key: secrets.key });
```
:point_right: The files with name `secrets.*.js` already are ignored on the repository.
## Resources:
- [Webpack documentation](https://webpack.js.org/concepts/)
- [Chrome Extension documentation](https://developer.chrome.com/extensions/getstarted)
---
Michael Xieyang Liu | [Website](https://lxieyang.github.io)
The website can then be accessed at http://localhost:3000/.

19779
package-lock.json generated

File diff suppressed because it is too large Load diff

97
package.json Executable file → Normal file
View file

@ -1,58 +1,55 @@
{
"name": "vytal",
"version": "1.0.0",
"description": "Vytal",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/z0ccc/Vytal.git"
},
"scripts": {
"build": "node utils/build.js",
"start": "node utils/webserver.js",
"prettier": "prettier --write '**/*.{js,jsx,ts,tsx,json,css,scss,md}'"
},
"homepage": "https://locatejs.com",
"name": "locatejs",
"version": "0.1.0",
"private": true,
"dependencies": {
"@hot-loader/react-dom": "^17.0.2",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"countries-and-timezones": "^3.2.2",
"country-language": "^0.1.7",
"gh-pages": "^3.2.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hot-loader": "^4.13.0"
"react-github-btn": "^1.2.1",
"react-modal": "^3.14.3",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"
},
"scripts": {
"predeploy": "yarn run build",
"deploy": "echo 'locatejs.com' > ./build/CNAME && gh-pages -d build",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/core": "^7.17.0",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@types/chrome": "^0.0.177",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.3",
"babel-preset-react-app": "^10.0.1",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^7.0.0",
"css-loader": "^6.6.0",
"eslint": "^8.8.0",
"eslint-config-react-app": "^7.0.0",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"file-loader": "^6.2.0",
"fs-extra": "^10.0.0",
"html-loader": "^3.1.0",
"html-webpack-plugin": "^5.5.0",
"node-sass": "^6.0.1",
"prettier": "^2.5.1",
"sass-loader": "^12.4.0",
"source-map-loader": "^3.0.1",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.1",
"ts-loader": "^9.2.6",
"typescript": "^4.5.5",
"webpack": "^5.68.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4"
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.25.1",
"eslint-plugin-react-hooks": "^4.2.0"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

89
public/frame.html Normal file
View file

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

93
public/index.html Normal file
View file

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

BIN
public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

25
public/manifest.json Normal file
View file

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

BIN
public/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

3
public/robots.txt Normal file
View file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

79
public/worker.js Normal file
View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="#fff" d="M256 64C256 46.33 270.3 32 288 32H415.1C415.1 32 415.1 32 415.1 32C420.3 32 424.5 32.86 428.2 34.43C431.1 35.98 435.5 38.27 438.6 41.3C438.6 41.35 438.6 41.4 438.7 41.44C444.9 47.66 447.1 55.78 448 63.9C448 63.94 448 63.97 448 64V192C448 209.7 433.7 224 416 224C398.3 224 384 209.7 384 192V141.3L214.6 310.6C202.1 323.1 181.9 323.1 169.4 310.6C156.9 298.1 156.9 277.9 169.4 265.4L338.7 96H288C270.3 96 256 81.67 256 64V64zM0 128C0 92.65 28.65 64 64 64H160C177.7 64 192 78.33 192 96C192 113.7 177.7 128 160 128H64V416H352V320C352 302.3 366.3 288 384 288C401.7 288 416 302.3 416 320V416C416 451.3 387.3 480 352 480H64C28.65 480 0 451.3 0 416V128z"/></svg>

Before

Width:  |  Height:  |  Size: 736 B

View file

@ -1,17 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid meet"
version="1"
viewBox="0 0 4389 1056"
>
<g fill="#fff" stroke="none" transform="matrix(.1 0 0 -.1 0 1056)">
<path d="M4890 10554c-19-2-102-11-185-20-814-84-1627-377-2332-841-666-437-1242-1047-1652-1748-345-590-582-1279-670-1950-34-261-44-422-44-710 0-388 28-679 99-1035C523 2160 2153 526 4240 106c360-73 640-99 1040-99 488 0 847 44 1305 160 665 168 1310 477 1860 891 1004 754 1703 1833 1983 3062 146 636 168 1351 61 2010-273 1699-1362 3160-2913 3911-540 261-1091 420-1716 495-100 12-240 17-535 19-220 2-416 1-435-1zm870-978c947-115 1772-494 2460-1130 303-281 615-672 815-1025 491-866 669-1875 504-2861-113-681-390-1322-814-1885-233-310-552-627-869-862-1529-1139-3637-1136-5161 7-391 294-746 669-1012 1070-367 553-599 1170-683 1814-113 869 22 1705 399 2472 103 210 151 294 287 499 316 476 726 886 1199 1199 340 225 629 368 999 495 349 120 681 187 1106 225 112 10 641-3 770-18z"></path>
<path d="M5045 8784c-729-59-1353-298-1925-740-149-114-480-445-594-594-405-524-638-1090-722-1755-22-172-25-595-6-760 86-742 352-1362 822-1916 176-207 457-462 565-513 153-73 342-56 483 44 140 98 210 245 199 420-9 156-62 251-211 377-479 408-770 907-876 1504-31 174-39 528-16 719 93 759 503 1435 1121 1847 275 183 609 315 946 372 442 76 912 34 1329-116 593-214 1089-664 1375-1248 194-396 270-726 270-1165-1-320-34-522-135-815-143-414-360-722-786-1116-311-287-141-788 284-834 71-8 180 13 256 51 97 47 353 285 519 482 474 563 751 1223 818 1954 17 188 6 625-19 798-99 656-347 1220-760 1730-118 144-361 385-514 507-520 416-1102 662-1770 748-118 16-543 28-653 19z"></path>
<path d="M5175 6514c-92-14-198-39-260-61-458-165-766-607-768-1103-1-309 89-544 306-792 141-160 207-284 259-481 21-80 22-101 27-972l6-890 24-60c79-199 237-332 440-372 282-56 561 117 657 407 17 51 19 120 24 935 6 867 6 881 28 958 51 183 137 343 254 474 207 230 308 486 308 782 0 328-118 612-349 842-171 170-340 263-576 314-69 16-321 28-380 19z"></path>
<path d="M39255 8795c-156-35-252-89-375-214-79-79-103-111-137-181-23-47-50-112-60-145-17-57-18-203-21-2915-2-2552-1-2863 13-2933 31-154 97-272 219-392 93-91 176-144 291-183l80-27 1972-3c2216-3 2045-8 2213 74 82 41 109 61 196 148s107 114 147 196c56 113 77 207 77 338-1 222-69 380-232 538-87 85-141 122-228 159-131 55-109 54-1270 57-591 2-1274 4-1517 6l-443 2-2 2433-3 2432-23 73c-86 279-301 480-574 536-92 19-240 20-323 1z"></path>
<path d="M12765 8744c-101-17-243-74-332-136-160-109-287-325-313-531-10-84-2-193 21-280 9-37 516-1310 1838-4620 424-1062 439-1096 540-1209 63-71 201-159 301-192 130-43 243-53 364-32 163 27 276 83 399 196 130 120 126 111 585 1263 231 578 735 1842 1120 2807 530 1328 705 1776 718 1840 59 295-87 628-344 784-226 137-480 151-719 41-147-68-257-165-333-294-21-36-377-914-790-1950l-753-1884-756 1890c-416 1039-772 1916-791 1949-48 81-170 202-255 252-114 67-240 104-365 107-58 2-118 1-135-1z"></path>
<path d="M26688 8740c-304-52-566-312-619-614-18-104-7-273 24-369 75-231 263-414 512-498l70-24 792-3 793-3 2-2442 3-2442 23-70c87-258 284-451 528-516 108-29 287-29 396-1 270 72 486 299 544 571 11 55 14 457 14 2484v2417h753c474 0 776 4 816 11 164 26 285 91 416 223 151 152 214 307 215 523 0 116-11 179-47 277-66 176-234 350-413 426-140 59 58 54-2465 56-1270 1-2331-2-2357-6z"></path>
<path d="M34355 8735c-232-43-433-192-538-402-33-64-2231-5575-2255-5652-13-42-17-90-17-196 1-131 3-146 32-227 78-220 214-370 416-455 112-47 194-64 307-65 143 0 264 32 382 103 119 70 223 181 285 304 12 22 357 879 768 1905s750 1868 754 1872c3 4 339-826 745-1845 406-1018 755-1885 775-1925 133-274 456-451 752-413 109 14 155 29 279 88 79 38 107 59 180 132 71 71 94 103 133 181 89 180 112 363 68 530-22 86-2217 5595-2263 5682-150 281-487 442-803 383z"></path>
<path d="M19760 8661c-129-35-236-96-337-194-264-253-298-670-80-966 23-31 467-625 987-1321l945-1265 5-1255 5-1255 24-70c65-194 179-338 343-437 106-63 202-90 343-95 153-6 238 11 364 73 169 83 316 254 379 441l27 78 5 1250 5 1250 1021 1280c561 704 1039 1305 1062 1335 94 127 137 261 135 430-2 240-95 431-283 581-141 113-312 167-495 156-199-13-348-76-478-205-29-28-421-512-872-1075l-819-1025-763 1018c-419 561-781 1040-805 1066-133 145-329 224-548 223-68 0-128-6-170-18z"></path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

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

@ -0,0 +1,166 @@
:root {
--main: #943ec5;
--text: rgb(0, 0, 0, 0.8);
--border: #ddd;
--grey: #c4c4c4;
--lightGrey: #f8f8f8;
--issueBackground: #f8d7da;
--issueText: #721c24;
--fill: #9fa6b2;
--link: #943ec5;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 24px;
font-size: 14px;
}
.background {
position: fixed;
width: 100%;
height: 100%;
background: var(--main);
background: linear-gradient(
165deg,
rgba(87, 35, 117, 1) 0%,
rgba(148, 62, 197, 1) 40%,
rgba(169, 100, 208, 1) 100%
);
z-index: -1;
}
.App {
min-height: 100vh;
display: flex;
flex-direction: column;
}
h1 {
margin: 0 0 10px 0;
font-weight: 600;
font-size: 16px;
word-break: break-all;
}
p {
margin: 10px 0 0 0;
}
b {
font-weight: 600;
}
.tableWrap {
border: 1px solid var(--border);
border-radius: 6px;
}
table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
font-size: inherit;
color: inherit;
line-height: inherit;
}
tr:not(:last-child) {
border-bottom: 1px solid var(--border);
}
td {
padding: 10px;
word-break: break-all;
}
td:first-child {
width: 100px;
font-weight: 600;
word-break: normal;
}
td:nth-child(3) {
width: 40px;
font-weight: 600;
word-break: normal;
}
ul {
padding-left: 20px;
margin: 0px;
}
.link {
color: var(--link);
text-decoration: none;
}
.link:hover {
text-decoration: underline;
}
.checkBox {
margin: 0 0 10px 0;
display: block;
}
input[type="checkbox"] {
margin: 0 6px 0 0;
}
.button {
display: block;
background-color: var(--main);
color: #fff;
border-radius: 6px;
box-sizing: border-box;
text-align: center;
width: 100%;
height: 46px;
border: none;
cursor: pointer;
font-family: inherit;
font-size: inherit;
-webkit-appearance: none;
}
.button:hover {
opacity: 0.7;
}
.boxWrap {
border: 1px solid var(--border);
border-radius: 6px;
padding: 10px;
}
.hash {
text-align: center;
font-weight: 600;
}
@media screen and (max-width: 575px) {
body {
font-size: 13px;
}
h1 {
font-size: 15px;
}
.contentIcon {
flex: none;
margin: 0 18px 0 0;
width: 28px !important;
}
td:first-child {
width: 70px;
}
}

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

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

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

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

23
src/components/Blocks.css Normal file
View file

@ -0,0 +1,23 @@
.contentBlock {
color: var(--text);
background-color: #fff;
border-radius: 6px;
box-sizing: border-box;
padding: 20px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
margin: 0 0 24px 0;
min-width: 500px;
}
.loadBlock {
margin: 0 12px 12px 12px !important;
}
@media screen and (max-width: 575px) {
.contentBlock {
padding: 18px;
width: 100%;
min-width: 0;
margin: 0 0 12px 0;
}
}

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

@ -0,0 +1,103 @@
import './Blocks.css';
import { useState, useEffect } from 'react';
import DataContext from './Context';
import DataBlock from './DataBlock';
import GeolocationBlock from './GeolocationBlock';
import delayedData from '../utils/data';
const getWebWorker = () => {
let w;
if (typeof w === 'undefined') {
w = new Worker('/worker.js');
}
return w;
};
const Blocks = () => {
const [workerData, setWorkerData] = useState();
const [frameData, setFrameData] = useState();
// eslint-disable-next-line no-undef
const initialData = initialDataObj;
useEffect(() => {
const frame = document.createElement('iframe');
document.body.appendChild(frame);
frame.style.display = 'none';
frame.src = '/frame.html';
const receiveMessage = (event) => {
if (event.data.type === 'frameData') {
setFrameData(event.data.data);
}
};
window.addEventListener('message', receiveMessage, false);
if (window.Worker.length) {
getWebWorker().onmessage = (event) => {
setWorkerData(event.data);
};
} else {
setWorkerData(true);
}
}, []);
return (
<>
{initialData && delayedData && frameData && workerData ? (
<DataContext.Provider
value={{
initialData,
delayedData,
frameData,
workerData,
}}
>
<div className="centerBlockInner">
<DataBlock
title="Intl.DateTimeFormat().resolvedOptions().timeZone"
type="timeZone"
/>
<DataBlock
title="Intl.DateTimeFormat().resolvedOptions().locale"
type="locale"
/>
<DataBlock title="navigator.userAgent" type="userAgent" />
</div>
<div className="centerBlockInner">
<DataBlock title="new Date().toString()" type="dateString" />
<DataBlock title="new Date().toLocaleString()" type="dateLocale" />
<DataBlock
title="new Date().getTimezoneOffset()"
type="timezoneOffset"
/>
<GeolocationBlock />
</div>
<div className="centerBlockMobile">
<DataBlock
title="Intl.DateTimeFormat().resolvedOptions().timeZone"
type="timeZone"
/>
<DataBlock
title="Intl.DateTimeFormat().resolvedOptions().locale"
type="locale"
/>
<DataBlock title="new Date().toString()" type="dateString" />
<DataBlock title="new Date().toLocaleString()" type="dateLocale" />
<DataBlock
title="new Date().getTimezoneOffset()"
type="timezoneOffset"
/>
<DataBlock title="navigator.userAgent" type="userAgent" />
<GeolocationBlock />
</div>
</DataContext.Provider>
) : (
<div className="contentBlock loadBlock">
<center>Loading...</center>
</div>
)}
</>
);
};
export default Blocks;

View file

@ -0,0 +1,29 @@
import { useContext } from 'react';
import DataContext from './Context';
import Block from './Block';
import { getConnection } from '../utils/connection';
import TableRow from './TableRow';
const ConnectionBlock = () => {
const { connectionData } = useContext(DataContext);
return (
<Block>
<h1>Connection</h1>
<div className="tableWrap">
<table>
<tbody>
{getConnection(connectionData).map((item) => (
<TableRow key={item.key} title={item.key} value={item.value} issues={item.issues} />
))}
</tbody>
</table>
</div>
<p>
Your IP address reveals information about your
connection. Using a VPN or Tor will hide your connection info.
</p>
</Block>
);
};
export default ConnectionBlock;

View file

@ -0,0 +1,5 @@
import { createContext } from 'react';
const DataContext = createContext();
export default DataContext;

View file

@ -0,0 +1,30 @@
import { useContext } from 'react';
import DataContext from './Context';
import Block from './Block';
import TableRow from './TableRow';
const DataBlock = ({ title, type }) => {
const { initialData, delayedData, frameData, workerData } = useContext(DataContext);
return (
<Block>
<h1>{title}</h1>
<div className="tableWrap">
<table>
<tbody>
<TableRow title="Initial" value={initialData[type].value} issues={initialData[type].issues} />
<TableRow title="Delayed" value={delayedData[type].value} issues={delayedData[type].issues} />
<TableRow title="Frame" value={frameData[type].value} issues={frameData[type].issues} />
<TableRow title="Web worker" value={window.Worker.length ? workerData[type].value : null} issues={window.Worker.length ? workerData[type].issues : ['Web workers blocked']} />
</tbody>
</table>
</div>
<p>
Date and language data can be used to identify your
location. Changing the settings on your computer can prevent this.
</p>
</Block>
);
};
export default DataBlock;

View file

@ -0,0 +1,60 @@
import { useState } from 'react';
import Block from './Block';
import getGeolocation from '../utils/geolocation';
import TableRow from './TableRow';
const GeolocationBlock = () => {
const [geolocationData, setGeolocationData] = useState();
const [buttonValue, setbuttonValue] = useState('Allow Geolocation API');
return (
<Block>
<h1>HTML Geolocation API</h1>
{geolocationData ? (
<>
{typeof geolocationData === 'string' ? (
<div className="boxWrap">
<div className="hash">{`${geolocationData}`}</div>
</div>
) : (
<>
<p style={{ marginBottom: '10px' }}>
This data is not included in location prediction
</p>
<div className="tableWrap">
<table>
<tbody>
{geolocationData.map((item) => (
<TableRow
key={item.key}
title={item.key}
value={item.value}
issues={item.issues}
/>
))}
</tbody>
</table>
</div>
</>
)}
</>
) : (
<input
type="submit"
onClick={() => {
getGeolocation(setGeolocationData);
setbuttonValue('Loading...');
}}
className="button"
value={buttonValue}
/>
)}
<p>
HTML Geolocation API is used to get the geographical position of a user.
Since this can compromise privacy, its not available unless the user
approves it.
</p>
</Block>
);
};
export default GeolocationBlock;

18
src/components/GitHub.css Normal file
View file

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

16
src/components/GitHub.js Normal file
View file

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

17
src/components/Logo.css Normal file
View file

@ -0,0 +1,17 @@
.logoWrap {
display: flex;
align-items: center;
justify-content: center;
}
.logo {
width: 225px;
margin: 24px 0 18px 0;
}
@media screen and (max-width: 575px) {
.logo {
width: 160px;
margin: 18px 0 12px 0;
}
}

12
src/components/Logo.js Normal file
View file

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

View file

@ -0,0 +1,25 @@
.centerBlockOuter {
display: flex;
justify-content: center;
gap: 24px;
}
.centerBlockInner {
max-width: 500px;
}
.centerBlockMobile {
max-width: 500px;
padding: 0 12px;
display: none;
}
@media screen and (max-width: 1100px) {
.centerBlockInner {
display: none;
}
.centerBlockMobile {
display: block;
}
}

View file

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

View file

@ -0,0 +1,16 @@
.mapImg {
width: 100%;
border-radius: 6px;
border: 1px solid var(--border);
box-sizing: border-box;
display: block;
margin: 0 0 10px 0;
background-color: var(--lightGrey);
aspect-ratio: 2 / 1;
}
@media screen and (max-width: 575px) {
.mapImg {
height: auto;
}
}

View file

@ -0,0 +1,44 @@
import { useContext } from 'react';
import DataContext from './Context';
import './PredictionBlock.css';
import Block from './Block';
import PredictionTableRow from './PredictionTableRow';
const PredictionBlock = () => {
const { prediction, mapUrl } = useContext(DataContext);
return (
<Block>
<h1>Location Prediction</h1>
<img
className="mapImg"
src={mapUrl}
alt="Map of location prediction"
/>
<div className="tableWrap">
<table>
<tbody>
<PredictionTableRow title="Country" value={prediction.country} percent={prediction.countryPercent} />
<PredictionTableRow title="City" value={prediction.city} percent={prediction.cityPercent} />
</tbody>
</table>
</div>
<p>
Accuracy of the location prediction is dependant on how much
authentic info you&apos;re exposing. To learn about how to hide your location visit the{' '}
<a
className="link"
target="_blank"
rel="noreferrer"
alt="Link to GitHub repo"
href="https://github.com/z0ccc/LocateJS#locatejs"
>
GitHub repo
</a>
.
</p>
</Block>
);
};
export default PredictionBlock;

View file

@ -0,0 +1,9 @@
const PredictionTableRow = ({ title, value, percent }) => (
<tr>
<td>{title}</td>
<td>{value || 'N/A'}</td>
<td>{percent}%</td>
</tr>
);
export default PredictionTableRow;

View file

@ -0,0 +1,36 @@
.circleButton {
display: flex;
width: 20px;
}
.modalHeader {
display: flex;
justify-content: space-between;
margin: 0 0 6px 0;
height: 20px;
}
.modalTitle {
font-weight: 600;
}
.closeButton {
fill: var(--border);
display: flex;
width: 12px;
cursor: pointer;
margin: 0 0 0 12px;
}
.closeButton:hover {
fill: var(--grey);
}
.issue {
cursor: pointer;
}
.issue:hover {
background-color: var(--issueBackground);
color: var(--issueText);
}

View file

@ -0,0 +1,69 @@
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 = ({ title, value, issues }) => {
const issuesExist = issues.length !== 0;
const [modalIsOpen, setIsOpen] = useState(false);
const dataValue = !value && value !== 0 ? 'null' : value;
const openModal = () => {
if (issuesExist) setIsOpen(true);
};
const closeModal = () => {
setIsOpen(false);
};
return (
<>
<tr className={issuesExist ? 'issue' : ''} onClick={openModal}>
<td>{title}</td>
<td>{Array.isArray(value) ? JSON.stringify(value) : dataValue}</td>
<td>
{issuesExist ? (
<XCircle className="circleButton" />
) : (
<CheckCircle className="circleButton" />
)}
</td>
</tr>
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
style={modalStyles}
contentLabel="Issues Modal"
>
<div className="modalHeader">
<div className="modalTitle">{title} issues</div>
<X className="closeButton" onClick={closeModal} />
</div>
<ul>
{issues.filter(Boolean).map((ele, index) => (
<li key={index}>{ele}</li>
))}
</ul>
</Modal>
</>
);
};
export default TableRow;

View file

@ -0,0 +1,25 @@
import Block from './Block';
import TableRow from './TableRow';
const TorBlock = ({ isTor }) => (
<Block>
<h1>Tor Browser</h1>
<div className="tableWrap">
<table>
<tbody>
<TableRow
title="Detected"
value={isTor ? 'True' : 'False'}
issues={[]}
/>
</tbody>
</table>
</div>
<p>
Tor Browser is a web browser that hides your location info,
causing the location prediction to be inaccurate.
</p>
</Block>
);
export default TorBlock;

View file

@ -0,0 +1,41 @@
import Block from './Block';
import TableRow from './TableRow';
import { getConnection } from '../utils/connection';
const WebRTCBlock = ({ data }) => (
<Block>
<h1>WebRTC Leaks</h1>
{data.length === 0 ? (
<div className="boxWrap">
<div className="hash">No WebRTC leaks found</div>
</div>
)
: (
<div style={{ display: 'grid', gap: '24px' }}>
{data.map((ipData) => (
<div className="tableWrap" key={ipData.query}>
<table>
<tbody>
{getConnection(ipData).map((item) => (
<TableRow
key={item.key}
title={item.key}
value={item.value}
issues={item.issues}
/>
))}
</tbody>
</table>
</div>
))}
</div>
)}
<p>
WebRTC leaks are a vulnerability that can expose your real IP address.
Using a quality VPN or disabling WebRTC will stop the leaks.
</p>
</Block>
);
export default WebRTCBlock;

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<g fill="#c3e6cb">
<path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 470 B

View file

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

After

Width:  |  Height:  |  Size: 712 B

5
src/images/github.svg Normal file
View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512">
<g fill="#fff">
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

48
src/images/logo.svg Normal file
View file

@ -0,0 +1,48 @@
<svg
version="1.0"
viewBox="0 0 2499.4541 511.75159"
preserveAspectRatio="xMidYMid meet">
<g
transform="matrix(0.1,0,0,-0.1,-5.0666667,511.8606)"
fill="#000000"
stroke="none"
id="g2722"
style="fill:#ffffff">
<path
d="M 1680,5105 C 1318,5059 988,4917 711,4685 387,4415 163,4035 80,3614 52,3477 42,3197 59,3065 139,2455 531,1669 1206,763 1435,456 1742,81 1806,31 c 56,-44 143,-39 201,12 48,42 315,363 467,560 757,988 1193,1837 1267,2471 17,142 7,403 -21,540 -85,430 -317,817 -654,1091 -389,315 -894,461 -1386,400 z m 373,-920 c 355,-62 640,-315 743,-660 36,-122 44,-313 19,-436 -58,-289 -241,-528 -502,-659 -229,-114 -515,-126 -753,-32 -152,61 -305,175 -399,298 -63,84 -133,223 -161,324 -28,96 -38,290 -21,396 82,505 572,857 1074,769 z"
id="path2704"
style="fill:#ffffff" />
<path
d="m 5156,4363 c -66,-17 -137,-96 -156,-173 -8,-32 -10,-516 -8,-1665 l 3,-1620 22,-40 c 25,-47 64,-85 113,-111 34,-18 79,-19 1071,-19 h 1036 l 48,30 c 75,47 109,101 113,185 2,38 -1,84 -8,105 -15,45 -59,96 -108,127 l -37,23 -885,5 -885,5 -5,1500 c -5,1375 -6,1503 -22,1530 -23,43 -77,91 -120,109 -35,15 -130,20 -172,9 z"
id="path2706"
style="fill:#ffffff" />
<path
d="m 16280,4359 c -45,-19 -89,-59 -117,-107 l -28,-47 -3,-462 -3,-461 -88,-4 c -77,-3 -94,-7 -137,-33 -136,-85 -152,-277 -31,-380 50,-43 90,-55 184,-55 h 72 l 4,-667 c 5,-720 5,-730 61,-895 91,-272 300,-440 619,-497 107,-19 263,-25 311,-11 99,27 170,121 169,225 0,69 -18,117 -62,167 -41,47 -95,68 -195,74 -102,8 -189,29 -242,59 -53,31 -121,105 -143,158 -39,90 -41,131 -41,767 v 618 l 183,4 182,3 46,33 c 125,90 138,263 27,369 -58,56 -89,63 -273,63 h -165 v 424 c 0,245 -4,446 -10,477 -13,65 -48,118 -103,155 -35,24 -54,29 -117,31 -41,2 -86,-2 -100,-8 z"
id="path2708"
style="fill:#ffffff" />
<path
d="m 22290,4354 c -42,-18 -95,-67 -118,-109 -16,-27 -17,-137 -22,-1260 -6,-1372 -1,-1270 -77,-1425 -49,-100 -169,-221 -268,-272 -106,-55 -174,-72 -295,-72 -178,-1 -328,60 -451,183 -115,115 -172,245 -188,426 -10,113 -21,142 -76,197 -108,107 -289,84 -370,-46 -26,-42 -30,-61 -33,-130 -8,-180 59,-414 166,-580 349,-543 1038,-697 1561,-349 223,149 392,379 465,635 45,155 47,253 44,1488 -3,1088 -4,1157 -21,1191 -25,50 -74,98 -122,120 -49,23 -146,24 -195,3 z"
id="path2710"
style="fill:#ffffff" />
<path
d="m 23855,4353 c -229,-49 -447,-200 -587,-406 -194,-286 -203,-695 -22,-999 67,-112 221,-267 329,-329 138,-80 216,-106 405,-134 242,-36 426,-163 520,-360 46,-97 60,-159 60,-273 0,-179 -63,-328 -189,-452 -80,-79 -152,-123 -261,-161 -84,-29 -241,-31 -345,-5 -147,38 -279,134 -391,287 -53,71 -107,99 -190,99 -165,0 -284,-159 -232,-311 45,-134 261,-346 453,-446 178,-92 325,-127 540,-126 141,0 172,3 260,27 116,30 293,115 379,180 279,212 424,469 456,808 32,337 -84,648 -334,893 -194,189 -385,281 -670,320 -215,30 -346,131 -416,322 -19,53 -22,77 -18,161 3,87 7,106 38,168 78,159 254,274 421,274 69,0 196,-36 251,-71 27,-18 70,-59 95,-93 114,-151 187,-197 286,-181 181,30 263,218 166,381 -19,31 -72,97 -118,146 -141,149 -307,244 -493,282 -97,20 -298,20 -393,-1 z"
id="path2712"
style="fill:#ffffff" />
<path
d="m 8660,3261 c -504,-93 -917,-501 -1016,-1006 -24,-123 -24,-377 -1,-498 49,-248 168,-466 353,-651 190,-190 419,-312 674,-357 97,-17 353,-17 450,0 316,56 605,236 803,500 89,119 136,208 182,344 123,368 71,787 -137,1104 -164,249 -411,440 -683,527 -170,54 -441,70 -625,37 z m 400,-473 c 244,-46 474,-237 574,-476 42,-99 58,-186 58,-307 0,-141 -19,-222 -83,-354 -45,-92 -62,-116 -148,-201 -161,-163 -309,-229 -531,-237 -237,-9 -407,53 -576,210 -127,119 -216,279 -245,442 -17,98 -7,282 21,370 47,152 144,295 264,391 192,154 420,209 666,162 z"
id="path2714"
style="fill:#ffffff" />
<path
d="m 11600,3266 c -366,-65 -661,-261 -868,-575 -250,-379 -264,-924 -35,-1316 120,-205 342,-415 548,-518 78,-39 216,-85 312,-104 121,-24 375,-24 496,0 214,43 421,142 566,273 67,60 90,109 91,190 0,129 -86,223 -211,232 -74,6 -123,-12 -204,-73 -83,-63 -227,-133 -310,-150 -89,-19 -260,-19 -347,0 -154,32 -268,96 -394,220 -102,101 -156,187 -201,320 -24,73 -27,94 -27,235 -1,136 2,164 22,230 100,318 360,533 688,570 198,22 399,-34 556,-155 31,-24 71,-51 90,-61 160,-84 348,28 348,205 -1,107 -51,174 -212,281 -138,93 -269,149 -433,185 -97,22 -378,28 -475,11 z"
id="path2716"
style="fill:#ffffff" />
<path
d="m 14175,3269 c -356,-52 -670,-255 -880,-569 -164,-245 -233,-549 -195,-860 65,-524 479,-976 991,-1080 203,-42 407,-36 608,16 110,29 287,111 385,178 75,52 86,50 86,-12 0,-116 125,-221 251,-209 65,6 104,24 149,69 72,72 70,46 70,708 0,633 -4,707 -51,862 -58,197 -165,375 -319,529 -200,201 -429,320 -700,364 -102,16 -297,18 -395,4 z m 373,-485 c 44,-10 113,-36 170,-65 165,-83 280,-199 361,-364 61,-123 83,-217 82,-355 0,-226 -86,-420 -254,-576 -116,-108 -233,-169 -380,-201 -109,-23 -298,-13 -402,21 -265,86 -468,303 -535,571 -26,101 -28,262 -4,366 34,156 146,341 262,435 204,166 438,222 700,168 z"
id="path2718"
style="fill:#ffffff" />
<path
d="m 18605,3274 c -300,-44 -517,-148 -722,-347 -209,-202 -332,-427 -378,-694 -20,-113 -19,-345 0,-459 63,-356 291,-679 621,-877 314,-188 767,-218 1116,-73 178,73 352,197 392,278 71,142 -6,306 -160,338 -74,16 -139,-4 -229,-71 -88,-65 -209,-122 -302,-143 -104,-23 -274,-21 -374,4 -192,49 -352,158 -465,317 -45,62 -104,180 -104,208 0,13 103,15 919,15 1014,0 971,-3 1039,63 70,67 83,120 71,286 -25,330 -169,614 -428,844 -163,145 -359,245 -571,292 -73,16 -359,29 -425,19 z m 372,-500 c 157,-45 261,-111 381,-239 71,-76 162,-231 162,-276 0,-18 -20,-19 -760,-19 -660,0 -760,2 -760,15 0,26 39,115 77,173 82,129 183,222 313,287 187,94 395,115 587,59 z"
id="path2720"
style="fill:#ffffff" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

1
src/images/predict.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Pro 5.15.3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M266.34 277.65l11.31-11.31c3.12-3.12 3.12-8.19 0-11.31L230.62 208l47.03-47.03c3.12-3.12 3.12-8.19 0-11.31l-11.31-11.31c-3.12-3.12-8.19-3.12-11.31 0l-64 64c-3.12 3.12-3.12 8.19 0 11.31l64 64c3.13 3.12 8.19 3.12 11.31-.01zm96-11.31l11.31 11.31c3.12 3.12 8.19 3.12 11.31 0l64-64c3.12-3.12 3.12-8.19 0-11.31l-64-64c-3.12-3.12-8.19-3.12-11.31 0l-11.31 11.31c-3.12 3.12-3.12 8.19 0 11.31L409.38 208l-47.03 47.03a7.994 7.994 0 0 0-.01 11.31zM624 368h-48V96c0-35.3-28.72-64-64-64H128c-35.28 0-64 28.7-64 64v272H16c-8.84 0-16 7.16-16 16v48c0 44.11 35.88 80 80 80h480c44.12 0 80-35.89 80-80v-48c0-8.84-7.16-16-16-16zM96 96c0-17.67 14.33-32 32-32h384c17.67 0 32 14.33 32 32v272H391.13c-4.06 0-7.02 3.13-7.92 7.09C379.98 389.35 367.23 400 352 400h-64c-15.23 0-27.98-10.65-31.21-24.91-.9-3.96-3.86-7.09-7.92-7.09H96V96zm512 336c0 26.47-21.53 48-48 48H80c-26.47 0-48-21.53-48-48v-32h194.75c6.59 18.62 24.38 32 45.25 32h96c20.88 0 38.66-13.38 45.25-32H608v32z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
src/images/system.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!-- Font Awesome Pro 5.15.3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M528 0H48C21.5 0 0 21.5 0 48v288c0 26.5 21.5 48 48 48h192l-24 96h-72c-8.8 0-16 7.2-16 16s7.2 16 16 16h288c8.8 0 16-7.2 16-16s-7.2-16-16-16h-72l-24-96h192c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48zM249 480l16-64h46l16 64h-78zm295-144c0 8.8-7.2 16-16 16H48c-8.8 0-16-7.2-16-16V48c0-8.8 7.2-16 16-16h480c8.8 0 16 7.2 16 16v288z"/></svg>

After

Width:  |  Height:  |  Size: 544 B

1
src/images/x.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"/></svg>

After

Width:  |  Height:  |  Size: 468 B

5
src/images/xCircle.svg Normal file
View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<g fill="#f4c1c6">
<path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 489 B

16
src/index.js Normal file
View file

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

View file

@ -1,20 +0,0 @@
{
"manifest_version": 3,
"name": "Vytal",
"description": "Vytal",
"permissions": ["storage", "debugger", "activeTab", "alarms"],
"background": { "service_worker": "background.bundle.js" },
"action": {
"default_popup": "popup.html",
"default_icon": "icon-32.png"
},
"icons": {
"128": "icon-128.png"
},
"web_accessible_resources": [
{
"resources": ["icon-128.png", "icon-32.png"],
"matches": []
}
]
}

View file

@ -1,115 +0,0 @@
import userAgents from '../../utils/userAgents'
const attachTab = (tabId) => {
chrome.storage.sync.get(
[
'ipData',
'timezone',
'timezoneMatchIP',
'lat',
'latitudeMatchIP',
'lon',
'longitudeMatchIP',
'locale',
'localeMatchIP',
'userAgent',
],
(result) => {
if (
result.timezone ||
result.lat ||
result.lon ||
result.locale ||
result.userAgent
) {
chrome.debugger.attach({ tabId: tabId }, '1.3', () => {
if (!chrome.runtime.lastError) {
if (result.timezone) {
chrome.debugger.sendCommand(
{ tabId: tabId },
'Emulation.setTimezoneOverride',
{
timezoneId: result.timezone,
},
() => {
if (
chrome.runtime.lastError &&
chrome.runtime.lastError.message.includes(
'Timezone override is already in effect'
)
) {
chrome.debugger.detach({ tabId })
attachTab(tabId)
}
}
)
}
if (result.locale) {
chrome.debugger.sendCommand(
{ tabId: tabId },
'Emulation.setLocaleOverride',
{
locale: result.locale,
}
)
}
if (result.lat || result.lon) {
chrome.debugger.sendCommand(
{ tabId: tabId },
'Emulation.setGeolocationOverride',
{
latitude: result.lat
? parseFloat(result.lat)
: result.ipData.lat,
longitude: result.lon
? parseFloat(result.lon)
: result.ipData.lon,
accuracy: 1,
}
)
}
if (result.userAgent) {
chrome.debugger.sendCommand(
{ tabId: tabId },
'Emulation.setUserAgentOverride',
{
userAgent: result.userAgent,
}
// { acceptLanguage: "en-CA" },
// { platform: "WebTV OS" }
)
// 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.69',
}
}
})
}
}
)
}
chrome.tabs.onUpdated.addListener((tabId, change, tab) => {
chrome.debugger.getTargets((tabs) => {
const currentTab = tabs.find((obj) => obj.tabId === tabId)
if (!currentTab.attached) {
attachTab(tabId)
}
})
})
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'userAgentAlarm') {
chrome.storage.sync.get(['randomUA'], (result) => {
if (result.randomUA) {
console.log('userAgentAlarm')
const randomUserAgent =
userAgents[Math.floor(Math.random() * userAgents.length)]
chrome.storage.sync.set({
userAgent: randomUserAgent,
})
}
})
}
})

View file

@ -1,59 +0,0 @@
import React, { useState, useEffect } from 'react'
import profiles from '../../utils/profiles'
import countryLocales from '../../utils/countryLocales'
const DebugSettings = ({ type, title, ip, profile, setProfile }) => {
const [value, setValue] = useState('')
useEffect(() => {
if (profile === 'none') {
setValue('')
chrome.storage.sync.set({ [type]: '' })
} else if (profile === 'match') {
if (ip) {
const ipTypeValue =
type === 'locale' ? countryLocales[ip.countryCode].locale : ip[type]
setValue(ipTypeValue)
chrome.storage.sync.set({ [type]: ipTypeValue })
}
} else if (profile === 'custom') {
chrome.storage.sync.get([type], (result) => {
console.log(type)
console.log(result)
result[type] && setValue(result[type])
})
} else if (profile !== 'default') {
setValue(profiles[profile][type])
chrome.storage.sync.set({ [type]: profiles[profile][type] })
}
}, [ip, profile, type])
const changeTextValue = (e) => {
chrome.storage.sync.set({ timezone: e.target.value })
setValue(e.target.value)
chrome.storage.sync.set({ profile: 'custom' })
setProfile('custom')
}
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
margin: '12px 0 0 0',
}}
>
<input
type="text"
value={value}
onChange={changeTextValue}
style={{
width: '206px',
}}
/>
<label>{title}</label>
</div>
)
}
export default DebugSettings

View file

@ -1,31 +0,0 @@
import React from 'react'
import detachDebugger from '../../utils/detachDebugger'
const getFlagEmoji = (countryCode) => {
const codePoints = countryCode
.toUpperCase()
.split('')
.map((char) => 127397 + char.charCodeAt())
return String.fromCodePoint(...codePoints)
}
const IpSettings = ({ ip, getIP, setIP }) => {
return (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div>
Current IP: {ip && `${ip.query} ${getFlagEmoji(ip.countryCode)}`}
</div>
<button
type="button"
onClick={() => {
Promise.resolve(getIP()).then((ipData) => setIP(ipData))
detachDebugger()
}}
>
Reload
</button>
</div>
)
}
export default IpSettings

View file

@ -1,47 +0,0 @@
import * as React from 'react'
// import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
// import { faExternalLinkAlt, faCog } from '@fortawesome/free-solid-svg-icons';
import Logo from '../../assets/img/logo.svg'
import Link from '../../assets/img/link.svg'
const Navbar = () => (
<div
style={{
width: '100%',
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'space-between',
padding: '8px 10px',
backgroundColor: 'var(--navbar)',
alignItems: 'center',
}}
>
<img
src={Logo}
style={{
width: '100px',
height: '24px',
}}
alt="Vytal logo"
/>
<a
href="https://locatejs.com"
target="_blank"
rel="noreferrer"
style={{
height: '18px',
}}
>
<img
src={Link}
alt="Test Vytal"
style={{
width: '18px',
height: '18px',
}}
/>
</a>
</div>
)
export default Navbar

View file

@ -1,84 +0,0 @@
import React, { useState, useEffect } from 'react'
import Navbar from './Navbar'
import IpSettings from './IpSettings'
import ProfileSelect from './ProfileSelect'
import DebugSettings from './DebugSettings'
import UserAgentSettings from './UserAgentSettings'
const getIP = () =>
fetch('http://ip-api.com/json/')
.then((response) => response.json())
.then((ipData) => {
chrome.storage.sync.set({ ipData })
return ipData
})
const Popup = () => {
const [ip, setIP] = useState(null)
const [profile, setProfile] = useState('default')
useEffect(() => {
chrome.storage.sync.get(['profile', 'ipData'], (result) => {
result.profile && setProfile(result.profile)
if (result.ipData) {
setIP(result.ipData)
} else {
Promise.resolve(getIP()).then((ipData) => setIP(ipData))
}
})
}, [])
return (
<div className="App">
<Navbar />
<div
style={{
padding: '12px',
}}
>
<IpSettings ip={ip} getIP={getIP} setIP={setIP} />
<ProfileSelect profile={profile} setProfile={setProfile} />
<DebugSettings
type="timezone"
title="Timezone"
ip={ip}
profile={profile}
setProfile={setProfile}
/>
<DebugSettings
type="locale"
title="Locale"
ip={ip}
profile={profile}
setProfile={setProfile}
/>
<DebugSettings
type="lat"
title="Latitude"
ip={ip}
profile={profile}
setProfile={setProfile}
/>
<DebugSettings
type="lon"
title="Longitude"
ip={ip}
profile={profile}
setProfile={setProfile}
/>
<UserAgentSettings ip={ip} type="lat" title="Latitude" />
<div
style={{
margin: '8px 0 0 0',
fontSize: '10px',
}}
>
Tabs need to be initialized for full protection.{' '}
<a href="/">Read more</a>
</div>
</div>
</div>
)
}
export default Popup

View file

@ -1,47 +0,0 @@
import React from 'react'
import profiles from '../../utils/profiles'
import detachDebugger from '../../utils/detachDebugger'
const ProfileSelect = ({ profile, setProfile }) => {
const changeProfile = (e) => {
detachDebugger()
chrome.storage.sync.set({
profile: e.target.value,
})
setProfile(e.target.value)
}
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
margin: '12px 0 0 0',
}}
>
<select
name="profile"
id="profile"
value={profile}
onChange={changeProfile}
style={{
width: '214px',
}}
>
<option value="none">None</option>
<option value="match">Match IP</option>
<option value="custom">Custom</option>
<optgroup label="Locations">
{Object.keys(profiles).map((key) => (
<option value={key} key={key}>
{profiles[key].name}
</option>
))}
</optgroup>
</select>
<label>Profile</label>
</div>
)
}
export default ProfileSelect

View file

@ -1,103 +0,0 @@
import React, { useState, useEffect } from 'react'
import userAgents from '../../utils/userAgents'
const UserAgentSettings = () => {
const [userAgent, setUserAgent] = useState('')
const [randomUA, setRandomUA] = useState(false)
const [interval, setInterval] = useState(60)
useEffect(() => {
chrome.storage.sync.get(['userAgent', 'randomUA', 'interval'], (result) => {
result.interval && setInterval(result.interval)
result.randomUA && setRandomUA(true)
if (result.userAgent) {
setUserAgent(result.userAgent)
}
})
}, [])
const changeUserAgent = (e) => {
chrome.storage.sync.set({ userAgent: e.target.value })
setUserAgent(e.target.value)
}
const randomize = (e) => {
const randomUserAgent =
userAgents[Math.floor(Math.random() * userAgents.length)]
chrome.storage.sync.set({
userAgent: randomUserAgent,
randomUA: e.target.checked,
})
e.target.checked ? setUserAgent(randomUserAgent) : setUserAgent('')
setRandomUA(e.target.checked)
if (parseInt(interval)) {
chrome.alarms.create('userAgentAlarm', {
delayInMinutes: parseInt(interval),
periodInMinutes: parseInt(interval),
})
}
}
const changeInterval = (e) => {
chrome.storage.sync.set({ interval: e.target.value })
setInterval(e.target.value)
if (parseInt(e.target.value)) {
chrome.alarms.create('userAgentAlarm', {
delayInMinutes: parseInt(e.target.value),
periodInMinutes: parseInt(e.target.value),
})
}
}
return (
<>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
margin: '12px 0 0 0',
}}
>
<input
type="text"
value={userAgent}
onChange={changeUserAgent}
style={{
width: '206px',
}}
/>
<label>User Agent</label>
</div>
<div
style={{
margin: '6px 0 0 0',
}}
>
<label
style={{
margin: '0 6px 0 0',
}}
>
<input type="checkbox" checked={randomUA} onChange={randomize} />
Randomize every
</label>
<label>
<input
type="number"
value={interval}
onChange={changeInterval}
style={{
width: '30px',
margin: '0 4px 0 0',
}}
/>
minutes
</label>
</div>
</>
)
}
export default UserAgentSettings

View file

@ -1,26 +0,0 @@
:root {
--main: #943ec5;
--text: #212121;
--background: #fff;
--scrollbar: #ccc;
--navbar: #943ec5;
--icon: #aab7b8;
--border: #f0f3f4;
}
body {
color: var(--text);
background-color: var(--background);
font-size: 13px;
line-height: 22px;
width: 315px;
margin: 0;
font-family: 'Segoe UI', Tahoma, sans-serif;
}
input[type=checkbox] {
vertical-align: middle;
position: relative;
bottom: 1px;
margin: 0 4px 0 0
}

View file

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title></title>
</head>
<body>
<div id="app-container"></div>
</body>
</html>

View file

@ -1,9 +0,0 @@
import React from 'react';
import { render } from 'react-dom';
import Popup from './Popup';
import './index.css';
render(<Popup />, window.document.querySelector('#app-container'));
if (module.hot) module.hot.accept();

13
src/reportWebVitals.js Normal file
View file

@ -0,0 +1,13 @@
// const reportWebVitals = (onPerfEntry) => {
// if (onPerfEntry && onPerfEntry instanceof Function) {
// import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
// getCLS(onPerfEntry);
// getFID(onPerfEntry);
// getFCP(onPerfEntry);
// getLCP(onPerfEntry);
// getTTFB(onPerfEntry);
// });
// }
// };
// export default reportWebVitals;

5
src/setupTests.js Normal file
View file

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

87
src/utils/connection.js Normal file
View file

@ -0,0 +1,87 @@
export { fetchAPI, getConnection };
// Gets connection values
const fetchAPI = (setData) => {
fetch('https://api.locatejs.com/')
.then((response) => response.json())
.then((json) => {
setData(json);
});
};
// Returns object with connection data
const getConnection = (connectionData) => {
const isProxy = checkProxy(connectionData);
return [
{
key: 'IP address',
value: connectionData.query,
issues: isProxy,
},
{
key: 'Country',
value: connectionData.country,
issues: isProxy,
},
{
key: 'Region',
value: connectionData.regionName,
issues: isProxy,
},
{
key: 'City',
value: connectionData.city,
issues: isProxy,
},
{
key: 'Time zone',
value: connectionData.timezone,
issues: isProxy,
},
{
key: 'Latitude',
value: connectionData.lat,
issues: isProxy,
},
{
key: 'Longitude',
value: connectionData.lon,
issues: isProxy,
},
{
key: 'ISP',
value: connectionData.isp,
issues: isProxy,
},
{
key: 'Org',
value: connectionData.org,
issues: isProxy,
},
{
key: 'ASN',
value: connectionData.as,
issues: isProxy,
},
];
};
const checkProxy = (data) => {
const issues = [];
if (data.security.vpn) {
issues.push('VPN has been detected');
}
if (data.security.proxy) {
issues.push('Proxy has been detected');
}
if (data.security.tor) {
issues.push('Tor node has been detected');
}
if (data.security.relay) {
issues.push('Private relay has been detected');
}
if (issues.length === 0 && data.proxy) {
issues.push('VPN/proxy has been detected');
}
return issues;
};

View file

@ -1,207 +0,0 @@
const countryLocales = {
AD: { locale: 'ca-AD' },
AE: { locale: 'ar-AE' },
AF: { locale: 'fa-AF' },
AG: { locale: 'en-AG' },
AL: { locale: 'sq-AL' },
AM: { locale: 'hy-AM' },
AO: { locale: 'ln-AO' },
AQ: { locale: 'en-US' },
AR: { locale: 'es-AR' },
AT: { locale: 'de-AT' },
AU: { locale: 'en-AU' },
AZ: { locale: 'az-AZ' },
BA: { locale: 'bs-BA' },
BB: { locale: 'en-BB' },
BD: { locale: 'bn-BD' },
BE: { locale: 'nl-BE' },
BF: { locale: 'fr-BF' },
BG: { locale: 'bg-BG' },
BH: { locale: 'ar-BH' },
BJ: { locale: 'fr-BJ' },
BI: { locale: 'fr-BI' },
BM: { locale: 'en-BM' },
BN: { locale: 'ms-BN' },
BR: { locale: 'pt-BR' },
BO: { locale: 'es-BO' },
BS: { locale: 'en-BS' },
BT: { locale: 'dz-BT' },
BW: { locale: 'en-BW' },
BY: { locale: 'be-BY' },
BZ: { locale: 'en-BZ' },
CA: { locale: 'en-CA' },
CD: { locale: 'fr-CD' },
CF: { locale: 'fr-CF' },
CG: { locale: 'fr-CG' },
CH: { locale: 'de-CH' },
CI: { locale: 'en-CA' },
CL: { locale: 'es-CL' },
CM: { locale: 'fr-CM' },
CN: { locale: 'zh-CN' },
CO: { locale: 'es-CO' },
CR: { locale: 'es-CR' },
CU: { locale: 'es-CU' },
CV: { locale: 'pt-CV' },
CY: { locale: 'el-CY' },
CZ: { locale: 'cs-CZ' },
DE: { locale: 'de-DE' },
DJ: { locale: 'fr-DJ' },
DK: { locale: 'da-DK' },
DM: { locale: 'en-DM' },
DO: { locale: 'es-DO' },
DZ: { locale: 'ar-DZ' },
EC: { locale: 'es-EC' },
EE: { locale: 'et-EE' },
EG: { locale: 'ar-EG' },
ES: { locale: 'es-ES' },
ER: { locale: 'ti-ER' },
ET: { locale: 'om-ET' },
FI: { locale: 'fi-FI' },
FJ: { locale: 'en-FJ' },
FM: { locale: 'en-FM' },
FR: { locale: 'fr-FR' },
GA: { locale: 'fr-GA' },
GB: { locale: 'en-GB' },
GD: { locale: 'en-GD' },
GE: { locale: 'ka-GE' },
GH: { locale: 'ak-GH' },
GM: { locale: 'en-GM' },
GN: { locale: 'fr-GN' },
GQ: { locale: 'es-GQ' },
GR: { locale: 'el-GR' },
GT: { locale: 'es-GT' },
GU: { locale: 'en-GU' },
GW: { locale: 'pt-GW' },
GY: { locale: 'en-GY' },
HK: { locale: 'zh-HK' },
HN: { locale: 'es-HN' },
HR: { locale: 'hr-HR' },
HT: { locale: 'fr-HT' },
HU: { locale: 'hu-HU' },
ID: { locale: 'id-ID' },
IE: { locale: 'en-IE' },
IL: { locale: 'he-IL' },
IN: { locale: 'hi-IN' },
IQ: { locale: 'ar-IQ' },
IR: { locale: 'fa-IR' },
IS: { locale: 'is-IS' },
IT: { locale: 'it-IT' },
JM: { locale: 'en-JM' },
JO: { locale: 'ar-JO' },
JP: { locale: 'ja-JP' },
KE: { locale: 'en-KE' },
KG: { locale: 'ky-KG' },
KI: { locale: 'en-KI' },
KH: { locale: 'km-KH' },
KM: { locale: 'fr-KM' },
KN: { locale: 'en-KN' },
KP: { locale: 'ko-KP' },
KW: { locale: 'ar-KW' },
KY: { locale: 'en-KY' },
KR: { locale: 'ko-KR' },
KZ: { locale: 'kk-KZ' },
LA: { locale: 'lo-LA' },
LB: { locale: 'ar-LB' },
LC: { locale: 'en-LC' },
LI: { locale: 'de-LI' },
LK: { locale: 'si-LK' },
LR: { locale: 'en-LR' },
LS: { locale: 'en-LS' },
LT: { locale: 'lt-LT' },
LU: { locale: 'fr-LU' },
LV: { locale: 'lv-LV' },
LY: { locale: 'ar-LY' },
MA: { locale: 'ar-MA' },
MC: { locale: 'fr-MC' },
MD: { locale: 'ro-MD' },
ME: { locale: 'sr-Latn-ME' },
MF: { locale: 'fr-MF' },
MG: { locale: 'fr-MG' },
MH: { locale: 'en-MH' },
MK: { locale: 'mk-MK' },
ML: { locale: 'bm-ML' },
MM: { locale: 'my-MM' },
MN: { locale: 'mn-MN' },
MO: { locale: 'zh-MO' },
MP: { locale: 'en-MP' },
MR: { locale: 'fr-MR' },
MT: { locale: 'mt-MT' },
MU: { locale: 'en-MU' },
MV: { locale: 'dv-MV' },
MW: { locale: 'en-MW' },
MX: { locale: 'es-MX' },
MY: { locale: 'ms-MY' },
MZ: { locale: 'pt-MZ' },
NA: { locale: 'af-NA' },
NL: { locale: 'nl-NL' },
NE: { locale: 'fr-NE' },
NG: { locale: 'en-NG' },
NC: { locale: 'nl-NL' },
NI: { locale: 'es-NI' },
NO: { locale: 'nb-NO' },
NP: { locale: 'ne-NP' },
NR: { locale: 'en-NR' },
NZ: { locale: 'en-NZ' },
OM: { locale: 'ar-OM' },
PA: { locale: 'es-PA' },
PE: { locale: 'es-PE' },
PH: { locale: 'en-PH' },
PG: { locale: 'en-PG' },
PK: { locale: 'ur-PK' },
PS: { locale: 'ar-PS' },
PL: { locale: 'pl-PL' },
PT: { locale: 'pt-PT' },
PW: { locale: 'en-PW' },
PY: { locale: 'es-PY' },
QA: { locale: 'ar-QA' },
RO: { locale: 'ro-RO' },
RS: { locale: 'sr-RS' },
RU: { locale: 'ru-RU' },
RW: { locale: 'fr-RW' },
SA: { locale: 'ar-SA' },
SB: { locale: 'en-SB' },
SC: { locale: 'en-SC' },
SD: { locale: 'en-SD' },
SE: { locale: 'sv-SE' },
SG: { locale: 'zh-SG' },
SI: { locale: 'sl-SI' },
SL: { locale: 'en-SL' },
SK: { locale: 'sk-SK' },
SM: { locale: 'it-SM' },
SN: { locale: 'fr-SN' },
SO: { locale: 'en-SO' },
SR: { locale: 'nl-SR' },
SS: { locale: 'en-SS' },
ST: { locale: 'pt-ST' },
SV: { locale: 'es-SV' },
SY: { locale: 'ar-SY' },
SZ: { locale: 'en-SZ' },
TD: { locale: 'ar-TD' },
TG: { locale: 'fr-TG' },
TH: { locale: 'th-TH' },
TJ: { locale: 'ru-TJ' },
TL: { locale: 'pt-TL' },
TM: { locale: 'tk-TM' },
TN: { locale: 'ar-TN' },
TO: { locale: 'en-TO' },
TR: { locale: 'tr-TR' },
TT: { locale: 'en-TT' },
TV: { locale: 'en-TV' },
TZ: { locale: 'en-TZ' },
TW: { locale: 'zh-TW' },
UA: { locale: 'uk-UA' },
UG: { locale: 'en-UG' },
US: { locale: 'en-US' },
UY: { locale: 'es-UY' },
VA: { locale: 'it-VA' },
VC: { locale: 'en-VC' },
VN: { locale: 'vi-VN' },
VU: { locale: 'en-VU' },
WS: { locale: 'en-WS' },
YE: { locale: 'ar-YE' },
ZA: { locale: 'en-ZA' },
ZM: { locale: 'en-ZM' },
ZW: { locale: 'en-ZW' },
}
export default countryLocales

91
src/utils/data.js Normal file
View file

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

View file

@ -1,11 +0,0 @@
const detachDebugger = () => {
chrome.debugger.getTargets((tabs) => {
for (const tab in tabs) {
if (tabs[tab].attached && tabs[tab].tabId) {
chrome.debugger.detach({ tabId: tabs[tab].tabId })
}
}
})
}
export default detachDebugger

30
src/utils/geolocation.js Normal file
View file

@ -0,0 +1,30 @@
const getGeolocation = (setGeolocationData) => {
new Promise((showPosition, showError) => {
navigator.geolocation.getCurrentPosition(showPosition, showError, { enableHighAccuracy: true });
}).then(async (position) => {
const geocode = await getGeocode(position.coords.latitude, position.coords.longitude);
setGeolocationData([
getObj('Latitude', position.coords.latitude),
getObj('Longitude', position.coords.longitude),
getObj('Accuracy', position.coords.accuracy),
getObj('Altitude', position.coords.altitude),
getObj('Altitude accuracy', position.coords.altitudeAccuracy),
getObj('Heading', position.coords.heading),
getObj('Speed', position.coords.speed),
getObj('Reverse geocoding', geocode),
]);
})
.catch((e) => setGeolocationData(e.message));
};
const getGeocode = (lat, long) => fetch(`https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${long}&key=AIzaSyByyRWGncal9iAq1-3Ek2WQ3ROLw9bCS-8`)
.then((response) => response.json())
.then((data) => data.results[0].formatted_address);
const getObj = (key, value) => ({
key,
value,
issues: [],
});
export default getGeolocation;

198
src/utils/predict.js Normal file
View file

@ -0,0 +1,198 @@
export { getMapUrl, getPrediction };
const ct = require('countries-and-timezones');
const getPrediction = (
initialData, delayedData, frameData, workerData, connectionData, webRTCData, isTor
) => {
let country, countryPercent, city, cityPercent, timeZone;
const getAccurateData = (type) => {
if (window.Worker.length && !workerData[type].issues.length) return workerData[type].value;
if (!frameData[type].issues.length) return frameData[type].value;
if (!delayedData[type].issues.length) return delayedData[type].value;
return initialData[type].value;
};
const accurateData = {
locale: getAccurateData('locale'),
timeZone: getAccurateData('timeZone'),
timezoneOffset: getAccurateData('timezoneOffset'),
dateString: getAccurateData('dateString'),
dateLocale: getAccurateData('dateLocale'),
language: getAccurateData('language'),
languages: getAccurateData('languages'),
};
const webRTCIP = checkWebRTC(webRTCData);
const systemCountry = checkCountry(accurateData);
const systemCity = checkCity(accurateData.timeZone, country);
if (!connectionData.isProxy) {
if (webRTCIP && !webRTCIP.isProxy) {
if (webRTCIP.query === connectionData.query) {
countryPercent = 90;
cityPercent = 90;
} else {
countryPercent = 80;
cityPercent = 80;
}
country = webRTCIP.countryCode;
city = webRTCIP.city;
timeZone = webRTCIP.timeZone;
} else {
country = connectionData.countryCode;
city = connectionData.city;
timeZone = connectionData.timeZone;
countryPercent = 80;
cityPercent = 80;
}
} else if (webRTCIP && !webRTCIP.isProxy) {
countryPercent = 85;
cityPercent = 85;
country = webRTCIP.countryCode;
city = webRTCIP.city;
timeZone = webRTCIP.timeZone;
}
if (country && systemCountry === country) {
countryPercent = 95;
} else if (country && systemCountry !== country) {
countryPercent -= 40;
} else {
countryPercent = 40;
country = systemCountry;
}
if (city && systemCity === city) {
cityPercent = 95;
} else if (city && systemCity !== city) {
cityPercent -= 20;
} else {
cityPercent = 20;
city = systemCity;
}
if (timeZone && timeZone !== accurateData.timeZone) {
countryPercent -= 20;
}
if (cityPercent > countryPercent) {
cityPercent = countryPercent - 5;
}
if (isTor) {
countryPercent = 5;
cityPercent = 5;
}
if (!city) cityPercent = 0;
const regionNames = new Intl.DisplayNames(['en'], { type: 'region' });
country = regionNames.of(country);
return { country, countryPercent, city, cityPercent };
};
const checkWebRTC = (webRTCData) => {
let localIP, ipv6, publicIP;
for (let i = 0; i < webRTCData.length; i++) {
if (webRTCData[i].proxy === false) {
if (webRTCData[i].query.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) {
localIP = webRTCData[i];
} else if (webRTCData[i].query.match(/^[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7}$/)) {
ipv6 = webRTCData[i];
} else {
publicIP = webRTCData[i];
}
}
}
if (publicIP) return publicIP;
if (ipv6) return ipv6;
if (localIP) return localIP;
return null;
};
const checkCountry = (data) => {
const countryArr =
checkLocale(data.locale).concat(checkTimezone(data.timeZone), checkLanguages(data));
const countryObj = handleCountryArr(countryArr);
const topCountry =
Object.keys(countryObj).reduce((a, b) => (countryObj[a] > countryObj[b] ? a : b));
return topCountry;
};
// Get country from locale
const checkLocale = (locale) => {
const IntlLocale = new Intl.Locale(locale);
// const regionNames = new Intl.DisplayNames(['en'], { type: 'region' });
return [IntlLocale.region];
};
// Get countries of timezone
const checkTimezone = (timeZone) => {
if (timeZone) {
const countryArr = ct.getTimezone(timeZone).countries;
return countryArr.concat(countryArr, countryArr);
}
return [];
};
const checkLanguages = (data) => {
const countryArr = [];
// loop thru system data languages
data.languages.forEach((language) => {
if (language.length > 2) {
countryArr.push(language.slice(-2));
}
});
// Checks if main language has country code
// if (data.language.length > 2) {
// countryArr.push(data.language.slice(-2));
// }
return countryArr;
};
// converts array to object of value/frequency
const handleCountryArr = (countryArr) =>
countryArr.reduce((obj, val) => {
// eslint-disable-next-line no-param-reassign
obj[val] = (obj[val] || 0) + 1;
return obj;
}, {});
const checkCity = (timeZone) => {
let city = null;
// Check if timezone contains city info
if (
timeZone.includes('/') &&
timeZone.match(/universal|GMT|UCT|UTC/g) === null &&
!/\d/.test(timeZone)
) {
city = timeZone.split('/');
city = city[city.length - 1].replace('_', ' ');
}
return city;
};
// Return url for static map image
const getMapUrl = (prediction) => {
let location, zoom;
if (!prediction.city) {
location = prediction.country;
zoom = 3;
} else {
location = `${prediction.city},${prediction.country}`;
zoom = 7;
}
return `https://maps.googleapis.com/maps/api/staticmap?${location}&markers=color:red%7Clabel:%7C${location}&size=460x230&zoom=${zoom}&key=AIzaSyB-YN-X8PGBSPd7NOaQu4csVhgJUnF3ZGk`;
};

View file

@ -1,236 +0,0 @@
const profiles = {
baghdad: {
name: 'Baghdad',
timezone: 'Asia/Baghdad',
locale: 'ar-IQ',
lat: 33.3152,
lon: 44.3661,
},
bangkok: {
name: 'Bangkok',
timezone: 'Asia/Bangkok',
locale: 'th-TH',
lat: 13.7563,
lon: 100.5018,
},
berlin: {
name: 'Berlin',
timezone: 'Europe/Berlin',
locale: 'de-DE',
lat: 52.520007,
lon: 13.404954,
},
cairo: {
name: 'Cairo',
timezone: 'Africa/Cairo',
locale: 'ar-EG',
lat: 30.0444,
lon: 31.2357,
},
delhi: {
name: 'Delhi',
timezone: 'Asia/Kolkata',
locale: 'hi-IN',
lat: 28.7041,
lon: 77.1025,
},
dubai: {
name: 'Dubai',
timezone: 'Asia/Dubai',
locale: 'ar-AE',
lat: 25.2048,
lon: 55.2708,
},
hongKong: {
name: 'Hong Kong',
timezone: 'Asia/Hong_Kong',
locale: 'zh-HK',
lat: 22.3193,
lon: 114.1694,
},
houston: {
name: 'Houston',
timezone: 'America/Chicago',
locale: 'en-US',
lat: 29.7627,
lon: -95.3863,
},
istanbul: {
name: 'Istanbul',
timezone: 'Europe/Istanbul',
locale: 'tr-TR',
lat: 41.0082,
lon: 28.9784,
},
jerusalem: {
name: 'Jerusalem',
timezone: 'Asia/Jerusalem',
locale: 'he-IL',
lat: 31.7683,
lon: 35.2137,
},
kyiv: {
name: 'Kyiv',
timezone: 'Europe/Kiev',
locale: 'uk-UA',
lat: 50.4501,
lon: 30.5234,
},
lagos: {
name: 'Lagos',
timezone: 'Africa/Lagos',
locale: 'en-NG',
lat: 6.5244,
lon: 3.379,
},
london: {
name: 'London',
timezone: 'Europe/London',
locale: 'en-GB',
lat: 51.5074,
lon: -0.1278,
},
losAngeles: {
name: 'Los Angeles',
timezone: 'America/Los_Angeles',
locale: 'en-US',
lat: 34.0522,
lon: -118.2437,
},
manila: {
name: 'Manila',
timezone: 'Asia/Manila',
locale: 'en-PH',
lat: 14.6042,
lon: 120.9822,
},
mecca: {
name: 'Mecca',
timezone: 'Asia/Riyadh',
locale: 'ar-SA',
lat: 21.4225,
lon: 39.8262,
},
mexicoCity: {
name: 'Mexico City',
timezone: 'America/Mexico_City',
locale: 'es-MX',
lat: 19.4271,
lon: -99.1276,
},
moscow: {
name: 'Moscow',
timezone: 'Europe/Moscow',
locale: 'ru-RU',
lat: 55.7558,
lon: 37.6176,
},
mumbai: {
name: 'Mumbai',
timezone: 'Asia/Kolkata',
locale: 'en-IN',
lat: 19.076,
lon: 72.8777,
},
newYork: {
name: 'New York',
timezone: 'America/New_York',
locale: 'en-US',
lat: 40.7127,
lon: -74.0059,
},
oslo: {
name: 'Oslo',
timezone: 'Europe/Oslo',
locale: 'no-NO',
lat: 59.9138,
lon: 10.7517,
},
paris: {
name: 'Paris',
timezone: 'Europe/Paris',
locale: 'fr-FR',
lat: 48.8566,
lon: 2.3522,
},
rome: {
name: 'Rome',
timezone: 'Europe/Rome',
locale: 'it-IT',
lat: 41.9028,
lon: 12.4964,
},
saoPaulo: {
name: 'São Paulo',
timezone: 'America/Sao_Paulo',
locale: 'pt-BR',
lat: -23.5505,
lon: -46.6333,
},
sanFrancisco: {
name: 'San Francisco',
timezone: 'America/Los_Angeles',
locale: 'en-US',
lat: 37.7749,
lon: -122.4194,
},
seoul: {
name: 'Seoul',
timezone: 'Asia/Seoul',
locale: 'ko-KR',
lat: 37.5665,
lon: 126.978,
},
shanghai: {
name: 'Shanghai',
timezone: 'Asia/Shanghai',
locale: 'zh-CN',
lat: 31.2304,
lon: 121.4737,
},
singapore: {
name: 'Singapore',
timezone: 'Asia/Singapore',
locale: 'en-SG',
lat: 1.3521,
lon: 103.8198,
},
sydney: {
name: 'Sydney',
timezone: 'Australia/Sydney',
locale: 'en-AU',
lat: -33.8688,
lon: 151.2195,
},
tokyo: {
name: 'Tokyo',
timezone: 'Asia/Tokyo',
locale: 'ja-JP',
lat: 35.6895,
lon: 139.6917,
},
vienna: {
name: 'Vienna',
timezone: 'Europe/Vienna',
locale: 'de-DE',
lat: 48.2082,
lon: 16.3738,
},
toronto: {
name: 'Toronto',
timezone: 'America/Toronto',
locale: 'en-CA',
lat: 43.6532,
lon: -79.3832,
},
washington: {
name: 'Washington',
timezone: 'America/New_York',
locale: 'en-US',
lat: 38.9072,
lon: -77.0369,
},
}
export default profiles

View file

@ -1,18 +0,0 @@
const userAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.50',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.44',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36 Edg/101.0.1210.32',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36',
]
export default userAgents

83
src/utils/webRTC.js Normal file
View file

@ -0,0 +1,83 @@
const getWebRTC = (setWebRTCData) => {
if (navigator.getUserMedia) {
const ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/;
// compatibility for firefox and chrome
let RTCPeerConnection = window.RTCPeerConnection
|| window.mozRTCPeerConnection
|| window.webkitRTCPeerConnection;
// bypass naive webrtc blocking using an iframe
if (!RTCPeerConnection) {
const frame = document.createElement('iframe');
document.body.appendChild(frame);
frame.style.display = 'none';
const win = frame.contentWindow;
RTCPeerConnection = win.RTCPeerConnection
|| win.mozRTCPeerConnection
|| win.webkitRTCPeerConnection;
}
// minimal requirements for data connection
const mediaConstraints = {
optional: [{ RtpDataChannels: true }]
};
const servers = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
// construct a new RTCPeerConnection
const pc = new RTCPeerConnection(servers, mediaConstraints);
let ips = [];
const ipsData = [];
// listen for candidate events
pc.onicecandidate = (ice) => {
// skip non-candidate events
if (ice.candidate) {
const ip = ipRegex.exec(ice.candidate.candidate);
if (ip !== null && ip.length > 1) {
ips.push(ip[1]);
}
}
};
// create a bogus data channel
pc.createDataChannel('');
// create an offer sdp
pc.createOffer((result) => {
// trigger the stun server request
pc.setLocalDescription(result, () => {}, () => {});
}, () => {});
const waitForElement = async () => {
if (pc.localDescription) {
const lines = pc.localDescription.sdp.split('\n');
lines.forEach((line) => {
if (line.indexOf('a=candidate:') === 0) {
const ip = ipRegex.exec(line);
if (ip !== null && ip.length > 1) {
ips.push(ip[1]);
}
}
});
ips = [...new Set(ips)];
for (let i = 0; i < ips.length; i++) {
ipsData.push(fetch(`https://api.locatejs.com/ip/${ips[i]}`)
.then((response) => response.json())
.then((json) => json));
}
setWebRTCData(await Promise.all(ipsData));
} else {
setTimeout(waitForElement, 1000);
}
};
waitForElement();
} else {
setWebRTCData([]);
}
};
export default getWebRTC;

View file

@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"noEmit": false,
"jsx": "react"
},
"include": ["src"],
"exclude": ["build", "node_modules"]
}

View file

@ -1,15 +0,0 @@
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
process.env.ASSET_PATH = '/';
var webpack = require('webpack'),
config = require('../webpack.config');
delete config.chromeExtensionBoilerplate;
config.mode = 'production';
webpack(config, function (err) {
if (err) throw err;
});

View file

@ -1,5 +0,0 @@
// tiny wrapper with default env vars
module.exports = {
NODE_ENV: process.env.NODE_ENV || 'development',
PORT: process.env.PORT || 3000,
};

View file

@ -1,60 +0,0 @@
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
process.env.ASSET_PATH = '/';
var WebpackDevServer = require('webpack-dev-server'),
webpack = require('webpack'),
config = require('../webpack.config'),
env = require('./env'),
path = require('path');
var options = config.chromeExtensionBoilerplate || {};
var excludeEntriesToHotReload = options.notHotReload || [];
for (var entryName in config.entry) {
if (excludeEntriesToHotReload.indexOf(entryName) === -1) {
config.entry[entryName] = [
'webpack/hot/dev-server',
`webpack-dev-server/client?hot=true&hostname=localhost&port=${env.PORT}`,
].concat(config.entry[entryName]);
}
}
config.plugins = [new webpack.HotModuleReplacementPlugin()].concat(
config.plugins || []
);
delete config.chromeExtensionBoilerplate;
var compiler = webpack(config);
var server = new WebpackDevServer(
{
https: false,
hot: false,
client: false,
host: 'localhost',
port: env.PORT,
static: {
directory: path.join(__dirname, '../build'),
},
devMiddleware: {
publicPath: `http://localhost:${env.PORT}/`,
writeToDisk: true,
},
headers: {
'Access-Control-Allow-Origin': '*',
},
allowedHosts: 'all',
},
compiler
);
if (process.env.NODE_ENV === 'development' && module.hot) {
module.hot.accept();
}
(async () => {
await server.start();
})();

View file

@ -1,174 +0,0 @@
var webpack = require('webpack'),
path = require('path'),
fileSystem = require('fs-extra'),
env = require('./utils/env'),
CopyWebpackPlugin = require('copy-webpack-plugin'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
TerserPlugin = require('terser-webpack-plugin')
var { CleanWebpackPlugin } = require('clean-webpack-plugin')
const ASSET_PATH = process.env.ASSET_PATH || '/'
var alias = {
'react-dom': '@hot-loader/react-dom',
}
// load the secrets
var secretsPath = path.join(__dirname, 'secrets.' + env.NODE_ENV + '.js')
var fileExtensions = [
'jpg',
'jpeg',
'png',
'gif',
'eot',
'otf',
'svg',
'ttf',
'woff',
'woff2',
]
if (fileSystem.existsSync(secretsPath)) {
alias['secrets'] = secretsPath
}
var options = {
mode: process.env.NODE_ENV || 'development',
entry: {
popup: path.join(__dirname, 'src', 'pages', 'Popup', 'index.jsx'),
background: path.join(__dirname, 'src', 'pages', 'Background', 'index.js'),
},
chromeExtensionBoilerplate: {
notHotReload: ['background'],
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'build'),
clean: true,
publicPath: ASSET_PATH,
},
module: {
rules: [
{
// look for .css or .scss files
test: /\.(css|scss)$/,
// in the `src` directory
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
{
test: new RegExp('.(' + fileExtensions.join('|') + ')$'),
type: 'asset/resource',
exclude: /node_modules/,
// loader: 'file-loader',
// options: {
// name: '[name].[ext]',
// },
},
{
test: /\.html$/,
loader: 'html-loader',
exclude: /node_modules/,
},
{ test: /\.(ts|tsx)$/, loader: 'ts-loader', exclude: /node_modules/ },
{
test: /\.(js|jsx)$/,
use: [
{
loader: 'source-map-loader',
},
{
loader: 'babel-loader',
},
],
exclude: /node_modules/,
},
],
},
resolve: {
alias: alias,
extensions: fileExtensions
.map((extension) => '.' + extension)
.concat(['.js', '.jsx', '.ts', '.tsx', '.css']),
},
plugins: [
new CleanWebpackPlugin({ verbose: false }),
new webpack.ProgressPlugin(),
// expose and write the allowed env vars on the compiled bundle
new webpack.EnvironmentPlugin(['NODE_ENV']),
new CopyWebpackPlugin({
patterns: [
{
from: 'src/manifest.json',
to: path.join(__dirname, 'build'),
force: true,
transform: function (content, path) {
// generates the manifest file using the package.json informations
return Buffer.from(
JSON.stringify({
description: process.env.npm_package_description,
version: process.env.npm_package_version,
...JSON.parse(content.toString()),
})
)
},
},
],
}),
new CopyWebpackPlugin({
patterns: [
{
from: 'src/assets/img/icon-128.png',
to: path.join(__dirname, 'build'),
force: true,
},
],
}),
new CopyWebpackPlugin({
patterns: [
{
from: 'src/assets/img/icon-32.png',
to: path.join(__dirname, 'build'),
force: true,
},
],
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'pages', 'Popup', 'index.html'),
filename: 'popup.html',
chunks: ['popup'],
cache: false,
}),
],
infrastructureLogging: {
level: 'info',
},
}
if (env.NODE_ENV === 'development') {
options.devtool = 'cheap-module-source-map'
} else {
options.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
}
}
module.exports = options

8911
yarn.lock

File diff suppressed because it is too large Load diff