├── .babelrc ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── files └── GeoLite2-Country.mmdb ├── package.json ├── postcss.config.js ├── public ├── fonts │ ├── Lato-Black.ttf │ ├── Lato-BlackItalic.ttf │ ├── Lato-Bold.ttf │ ├── Lato-BoldItalic.ttf │ ├── Lato-Hairline.ttf │ ├── Lato-HairlineItalic.ttf │ ├── Lato-Italic.ttf │ ├── Lato-Light.ttf │ ├── Lato-LightItalic.ttf │ └── Lato-Regular.ttf ├── icons │ └── icon.ico ├── index.html └── styles │ ├── Elements.postcss │ ├── Footer.postcss │ ├── Icons.postcss │ ├── Main.postcss │ ├── Results.postcss │ └── Update.postcss ├── src ├── actions │ ├── MainActions.js │ ├── ResultsActions.js │ └── UpdateActions.js ├── components │ ├── Footer.jsx │ ├── ResultsCountryItem.jsx │ └── Root.jsx ├── constants │ └── ActionTypes.js ├── containers │ ├── Main.jsx │ ├── Results.jsx │ └── Update.jsx ├── core │ ├── country.js │ └── updater.js ├── index.js ├── misc │ ├── other.js │ └── text.js ├── renderer.dev.js ├── renderer.js └── store │ ├── index.js │ └── reducers │ ├── index.js │ ├── results.js │ └── update.js ├── webpack.config.base.js ├── webpack.config.main.babel.js └── webpack.config.renderer.babel.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/env", "@babel/react"], 3 | "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-transform-runtime", ["@babel/plugin-proposal-object-rest-spread", { "useBuiltIns": false }]], 4 | "env": { 5 | "development": { 6 | "plugins": ["react-hot-loader/babel"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | public/styles/* linguist-vendored -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | /public/*.js 4 | /.vscode 5 | dist 6 | package-lock.json 7 | tests -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 assnctr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 6 | 7 | ## Features 8 | - Remove duplicates 9 | - Sort your proxy list by countries 10 | - Simple export in `ip` : `port` 11 | - Automatically checking for updates 12 | 13 | ## Updates 14 | Automatically checking for updates and notification if the latest version is available. 15 | 16 | #### Open Proxy Space 17 | [Premium](https://openproxy.space/premium) - Buy Proxy List 18 | [Free Proxy List](https://openproxy.space/list) - Always Updated Proxy Lists 19 | -------------------------------------------------------------------------------- /files/GeoLite2-Country.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/files/GeoLite2-Country.mmdb -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unfx-proxy-to-country", 3 | "version": "1.0.0", 4 | "main": "public/main.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "run-p build:*", 8 | "build:main": "cross-env NODE_ENV=production webpack -p --config webpack.config.main.babel.js", 9 | "build:renderer": "cross-env NODE_ENV=production webpack -p --config webpack.config.renderer.babel.js", 10 | "start": "run-p start:*", 11 | "start:main": "electron --require @babel/register src/index", 12 | "start:renderer": "cross-env NODE_ENV=development webpack-dev-server -d --config webpack.config.renderer.babel.js", 13 | "package": "npm run build && electron-builder --win", 14 | "publish": "npm run build && electron-builder --linux --win --publish always" 15 | }, 16 | "devDependencies": { 17 | "@babel/cli": "^7.1.5", 18 | "@babel/core": "^7.1.6", 19 | "@babel/plugin-proposal-class-properties": "^7.1.0", 20 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0", 21 | "@babel/plugin-transform-runtime": "^7.1.0", 22 | "@babel/preset-env": "^7.1.6", 23 | "@babel/preset-react": "^7.0.0", 24 | "@babel/register": "^7.0.0", 25 | "@babel/runtime": "^7.1.5", 26 | "babel-loader": "^8.0.4", 27 | "cross-env": "^5.2.0", 28 | "css-loader": "^1.0.1", 29 | "electron": "^4.0.0", 30 | "electron-builder": "^20.36.2", 31 | "electron-devtools-installer": "^2.2.4", 32 | "electron-react-devtools": "^0.5.3", 33 | "file-loader": "^2.0.0", 34 | "js-flock": "^3.5.3", 35 | "mmdb-reader": "*", 36 | "npm-run-all": "^4.1.3", 37 | "postcss-color-mod-function": "^2.4.3", 38 | "postcss-loader": "^3.0.0", 39 | "postcss-preset-env": "^5.3.0", 40 | "react": "^16.6.3", 41 | "react-dom": "^16.6.3", 42 | "react-hot-loader": "^4.3.12", 43 | "react-markdown": "^4.0.3", 44 | "react-redux": "^5.1.1", 45 | "redux": "^4.0.1", 46 | "redux-thunk": "^2.3.0", 47 | "request-progress": "^3.0.0", 48 | "request-promise": "^4.2.2", 49 | "style-loader": "^0.23.1", 50 | "url-loader": "^1.1.2", 51 | "webpack": "^4.25.1", 52 | "webpack-cli": "^3.1.2", 53 | "webpack-dev-server": "^3.1.10" 54 | }, 55 | "build": { 56 | "appId": "com.github.assnctr.unfxproxytocountry", 57 | "win": { 58 | "target": [ 59 | { 60 | "target": "zip", 61 | "arch": [ 62 | "x64", 63 | "ia32" 64 | ] 65 | }, 66 | { 67 | "target": "nsis-web", 68 | "arch": [ 69 | "x64", 70 | "ia32" 71 | ] 72 | }, 73 | { 74 | "target": "portable", 75 | "arch": [ 76 | "x64", 77 | "ia32" 78 | ] 79 | } 80 | ], 81 | "icon": "/public/icons/icon.ico", 82 | "artifactName": "${name}-v${version}-${arch}-${os}.${ext}" 83 | }, 84 | "linux": { 85 | "target": [ 86 | { 87 | "target": "zip", 88 | "arch": [ 89 | "x64", 90 | "ia32", 91 | "arm64", 92 | "armv7l" 93 | ] 94 | } 95 | ], 96 | "icon": "/public/icons/icon.ico", 97 | "artifactName": "${name}-v${version}-${arch}-${os}.${ext}" 98 | }, 99 | "publish": [ 100 | { 101 | "provider": "github", 102 | "owner": "assnctr", 103 | "repo": "unfx-proxy-to-country", 104 | "private": false 105 | } 106 | ], 107 | "productName": "Unfx Proxy to Country", 108 | "copyright": "2019 assnctr (openproxy.space)", 109 | "extraResources": [ 110 | "./files/**" 111 | ], 112 | "portable": { 113 | "artifactName": "${name}-v${version}-${arch}-${os}-portable.${ext}" 114 | }, 115 | "nsisWeb": { 116 | "oneClick": false, 117 | "perMachine": true, 118 | "allowToChangeInstallationDirectory": true, 119 | "differentialPackage": true 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-preset-env': { 4 | stage: 4, 5 | features: { 6 | 'nesting-rules': true 7 | } 8 | }, 9 | 'postcss-color-mod-function': {} 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /public/fonts/Lato-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/fonts/Lato-Black.ttf -------------------------------------------------------------------------------- /public/fonts/Lato-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/fonts/Lato-BlackItalic.ttf -------------------------------------------------------------------------------- /public/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/Lato-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/fonts/Lato-BoldItalic.ttf -------------------------------------------------------------------------------- /public/fonts/Lato-Hairline.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/fonts/Lato-Hairline.ttf -------------------------------------------------------------------------------- /public/fonts/Lato-HairlineItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/fonts/Lato-HairlineItalic.ttf -------------------------------------------------------------------------------- /public/fonts/Lato-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/fonts/Lato-Italic.ttf -------------------------------------------------------------------------------- /public/fonts/Lato-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/fonts/Lato-Light.ttf -------------------------------------------------------------------------------- /public/fonts/Lato-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/fonts/Lato-LightItalic.ttf -------------------------------------------------------------------------------- /public/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /public/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openproxyspace/unfx-proxy-to-country/716ef4bc161a23cfce09ecab57db9c0d64cc795f/public/icons/icon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Unfx Proxy to Country 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/styles/Elements.postcss: -------------------------------------------------------------------------------- 1 | :root { 2 | --green-color: #31bc86; 3 | --grey-color: #8c99a7; 4 | --blue-color: #5396d8; 5 | } 6 | 7 | ::-webkit-scrollbar { 8 | width: 15px; 9 | border-radius: 0.25em; 10 | } 11 | 12 | ::-webkit-scrollbar-thumb { 13 | background-color: color-mod(var(--blue-color) alpha(15%)); 14 | border-radius: 0.25em; 15 | } 16 | 17 | ::-webkit-scrollbar-track { 18 | background-color: #fafafa; 19 | } 20 | 21 | /* -------------------------------- 22 | Buttons section 23 | -------------------------------- */ 24 | 25 | button { 26 | font-weight: 900; 27 | font-size: 1em; 28 | border-radius: 1.75em; 29 | outline: 0; 30 | border: 0; 31 | cursor: pointer; 32 | padding: 0.75em 1.75em; 33 | margin-right: 2em; 34 | background-color: color-mod(var(--green-color) alpha(15%)); 35 | color: color-mod(var(--green-color) alpha(75%)); 36 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1); 37 | box-shadow: 0 0.25em 1em color-mod(var(--green-color) alpha(5%)); 38 | 39 | &:hover { 40 | color: rgba(255, 255, 255, 0.75); 41 | background-color: rgba(49, 188, 135, 0.65); 42 | transform: translateY(-5%); 43 | } 44 | 45 | &:active { 46 | transform: translateY(7.5%); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /public/styles/Footer.postcss: -------------------------------------------------------------------------------- 1 | :root { 2 | --green-color: #31bc86; 3 | --grey-color: #8c99a7; 4 | --blue-color: #5396d8; 5 | } 6 | 7 | .footer { 8 | background-color: #fff; 9 | padding: 2em; 10 | border-radius: 0.5em; 11 | box-shadow: 0 0.25em 1em color-mod(var(--green-color) alpha(5%)); 12 | display: flex; 13 | 14 | & .marks { 15 | display: flex; 16 | align-items: center; 17 | width: 60%; 18 | 19 | & .product-name { 20 | font-weight: 700; 21 | color: color-mod(var(--blue-color) alpha(50%)); 22 | } 23 | 24 | & .version { 25 | color: color-mod(var(--blue-color) alpha(50%)); 26 | } 27 | 28 | & a { 29 | font-weight: 900; 30 | margin-right: 1em; 31 | transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1); 32 | 33 | &:hover { 34 | text-decoration: underline; 35 | color: color-mod(var(--green-color) alpha(75%)); 36 | } 37 | 38 | &.copyright { 39 | color: var(--grey-color); 40 | font-weight: 400; 41 | text-decoration: none; 42 | 43 | &:hover { 44 | color: #0088cc; 45 | 46 | & svg { 47 | fill: #0088cc; 48 | } 49 | } 50 | 51 | & svg { 52 | width: 1em; 53 | height: 1em; 54 | fill: #8c99a7; 55 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1); 56 | } 57 | } 58 | } 59 | 60 | & span, 61 | & a { 62 | color: color-mod(var(--green-color) alpha(50%)); 63 | } 64 | 65 | & * { 66 | display: flex; 67 | align-content: center; 68 | align-items: center; 69 | margin-right: 0.5em; 70 | } 71 | } 72 | 73 | & .socials { 74 | display: flex; 75 | align-items: center; 76 | align-content: center; 77 | justify-content: flex-end; 78 | width: 40%; 79 | 80 | & a { 81 | display: flex; 82 | align-items: center; 83 | align-content: center; 84 | 85 | &:not(:last-child) { 86 | margin-right: 2em; 87 | } 88 | 89 | &.become-a-patron { 90 | cursor: pointer; 91 | font-weight: 700; 92 | user-select: none; 93 | text-decoration: none; 94 | color: #8c99a7; 95 | 96 | & svg { 97 | width: 1.2em; 98 | height: 1.2em; 99 | margin-left: 0.5em; 100 | } 101 | 102 | & span { 103 | margin: 0 0.12em; 104 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1); 105 | } 106 | 107 | &:hover svg { 108 | & circle { 109 | fill: #f96854; 110 | } 111 | 112 | & rect { 113 | fill: #052d49; 114 | } 115 | } 116 | } 117 | 118 | &.donate { 119 | width: 1.2em; 120 | height: 1.2em; 121 | 122 | &:hover { 123 | & path:nth-child(1) { 124 | fill: #ff3f2e; 125 | } 126 | 127 | & path:nth-child(2) { 128 | fill: #f2d1a5; 129 | } 130 | 131 | & path:nth-child(3) { 132 | fill: #1ea1e3; 133 | } 134 | 135 | & path:nth-child(4), 136 | & path:nth-child(5) { 137 | fill: #f2bb88; 138 | } 139 | 140 | & path:nth-child(6) { 141 | fill: #cf0404; 142 | } 143 | } 144 | } 145 | } 146 | 147 | & svg { 148 | height: 1.5em; 149 | width: 1.5em; 150 | cursor: pointer; 151 | fill: var(--grey-color); 152 | 153 | &:hover.github-svg path { 154 | fill: #333; 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /public/styles/Main.postcss: -------------------------------------------------------------------------------- 1 | :root { 2 | --green-color: #31bc86; 3 | --grey-color: #8c99a7; 4 | --blue-color: #5396d8; 5 | } 6 | 7 | @font-face { 8 | font-family: 'Lato'; 9 | font-style: normal; 10 | font-weight: 300; 11 | font-display: auto; 12 | src: local('Lato Light'), local('Lato-Light'), url('../fonts/Lato-Light.ttf') format('truetype'); 13 | } 14 | 15 | @font-face { 16 | font-family: 'Lato'; 17 | font-style: normal; 18 | font-weight: 400; 19 | font-display: auto; 20 | src: local('Lato Regular'), local('Lato-Regular'), url('../fonts/Lato-Regular.ttf') format('truetype'); 21 | } 22 | 23 | @font-face { 24 | font-family: 'Lato'; 25 | font-style: normal; 26 | font-weight: 700; 27 | font-display: auto; 28 | src: local('Lato Bold'), local('Lato-Bold'), url('../fonts/Lato-Bold.ttf') format('truetype'); 29 | } 30 | 31 | body { 32 | padding: 0; 33 | margin: 0; 34 | font-family: 'Lato'; 35 | text-rendering: geometricPrecision; 36 | -webkit-font-smoothing: antialiased; 37 | -moz-font-smoothing: grayscale; 38 | font-smoothing: antialiased; 39 | } 40 | 41 | input, 42 | textarea, 43 | button { 44 | font-family: 'Lato'; 45 | text-rendering: geometricPrecision; 46 | -webkit-font-smoothing: antialiased; 47 | -moz-font-smoothing: grayscale; 48 | font-smoothing: antialiased; 49 | } 50 | 51 | *, 52 | :after, 53 | :before { 54 | -webkit-box-sizing: border-box; 55 | -moz-box-sizing: border-box; 56 | box-sizing: border-box; 57 | } 58 | 59 | .main-page-container { 60 | position: fixed; 61 | display: flex; 62 | flex-flow: column wrap; 63 | align-items: center; 64 | padding: 2em; 65 | width: 100%; 66 | height: 100%; 67 | top: 0; 68 | left: 0; 69 | justify-content: center; 70 | overflow-y: auto; 71 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1); 72 | background-image: linear-gradient(129deg, color-mod(var(--green-color) alpha(35%)), color-mod(var(--blue-color) alpha(35%))); 73 | } 74 | -------------------------------------------------------------------------------- /public/styles/Results.postcss: -------------------------------------------------------------------------------- 1 | :root { 2 | --green-color: #31bc86; 3 | --grey-color: #8c99a7; 4 | --blue-color: #5396d8; 5 | } 6 | 7 | .results-container { 8 | position: fixed; 9 | display: flex; 10 | flex-flow: column nowrap; 11 | align-items: center; 12 | padding: 2em; 13 | width: 100%; 14 | height: 100%; 15 | top: 0; 16 | left: 0; 17 | overflow-y: auto; 18 | background-color: #fafafa; 19 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1); 20 | opacity: 0; 21 | visibility: hidden; 22 | 23 | &.active { 24 | opacity: 1; 25 | visibility: visible; 26 | 27 | & .results-content { 28 | opacity: 1; 29 | transform: translateY(0%); 30 | } 31 | } 32 | 33 | & .results-content { 34 | max-width: 1200px; 35 | width: 100%; 36 | opacity: 0; 37 | transform: translateY(-20%); 38 | transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1); 39 | transition-delay: transform 0.3s; 40 | 41 | & .content-header { 42 | display: flex; 43 | flex-flow: row wrap; 44 | margin-bottom: 1em; 45 | background-color: #fff; 46 | padding: 2em; 47 | border-radius: 0.5em; 48 | box-shadow: 0 0.25em 1em color-mod(var(--green-color) alpha(5%)); 49 | 50 | & .selected-counts { 51 | display: flex; 52 | align-items: center; 53 | color: #fff; 54 | font-weight: 700; 55 | user-select: none; 56 | 57 | & span { 58 | font-weight: 700; 59 | border-radius: 1.75em; 60 | margin-right: 1em; 61 | padding: 0.5em 1.25em; 62 | transition: background-color 0.7s cubic-bezier(0.23, 1, 0.32, 1); 63 | 64 | &:nth-child(odd) { 65 | background-color: color-mod(var(--green-color) alpha(75%)); 66 | } 67 | 68 | &:nth-child(even) { 69 | background-color: color-mod(var(--blue-color) alpha(75%)); 70 | } 71 | } 72 | 73 | &.no-items span { 74 | background-color: color-mod(var(--grey-color) alpha(65%)); 75 | } 76 | } 77 | 78 | & .controls { 79 | margin-left: auto; 80 | align-items: center; 81 | display: flex; 82 | 83 | & svg { 84 | width: 1.5em; 85 | height: 1.5em; 86 | transition: fill 0.5s cubic-bezier(0.23, 1, 0.32, 1), transform 0.3s cubic-bezier(0.23, 1, 0.32, 1); 87 | fill: color-mod(var(--grey-color) alpha(35%)); 88 | cursor: pointer; 89 | margin-left: 2em; 90 | 91 | &:hover { 92 | transform: translateY(-5%); 93 | 94 | &:nth-child(odd) { 95 | fill: color-mod(var(--green-color) alpha(75%)); 96 | } 97 | 98 | &:nth-child(even) { 99 | fill: color-mod(var(--blue-color) alpha(75%)); 100 | } 101 | } 102 | 103 | &:active { 104 | transform: translateY(7.5%); 105 | } 106 | } 107 | } 108 | } 109 | 110 | & .tip { 111 | display: flex; 112 | align-items: center; 113 | margin-top: 1em; 114 | margin-bottom: 1.25em; 115 | user-select: none; 116 | background-color: #fff; 117 | box-shadow: 0 0.25em 1em color-mod(var(--green-color) alpha(5%)); 118 | padding: 2em; 119 | border-radius: 0.5em; 120 | 121 | & svg { 122 | width: 1.75em; 123 | height: 1.75em; 124 | fill: color-mod(var(--grey-color) alpha(75%)); 125 | margin-right: 1em; 126 | } 127 | 128 | & span { 129 | font-size: 0.9em; 130 | color: color-mod(var(--grey-color) alpha(75%)); 131 | 132 | & b { 133 | margin-right: 0.5em; 134 | } 135 | } 136 | } 137 | 138 | & .countries { 139 | display: flex; 140 | flex-flow: row wrap; 141 | justify-content: space-between; 142 | text-align: left; 143 | box-shadow: 0 0.25em 1em color-mod(var(--green-color) alpha(5%)); 144 | background-color: #fff; 145 | padding: 2em 2em 1em 2em; 146 | border-radius: 0.5em; 147 | 148 | & .country-item { 149 | display: flex; 150 | user-select: none; 151 | cursor: pointer; 152 | width: 25%; 153 | margin-bottom: 1em; 154 | overflow: hidden; 155 | align-items: center; 156 | transition: color 0.5s cubic-bezier(0.23, 1, 0.32, 1); 157 | color: color-mod(var(--grey-color) alpha(50%)); 158 | 159 | &.active { 160 | &:nth-child(odd) { 161 | color: color-mod(var(--green-color) alpha(75%)); 162 | 163 | & .ico-wrap { 164 | border: 1px dashed color-mod(var(--green-color) alpha(65%)); 165 | } 166 | } 167 | 168 | &:nth-child(even) { 169 | color: color-mod(var(--blue-color) alpha(75%)); 170 | 171 | & .ico-wrap { 172 | border: 1px dashed color-mod(var(--blue-color) alpha(65%)); 173 | } 174 | } 175 | 176 | & .count { 177 | color: color-mod(var(--grey-color) alpha(75%)); 178 | } 179 | } 180 | 181 | & .ico-wrap { 182 | margin-right: 0.5em; 183 | border-radius: 1em; 184 | padding: 2px; 185 | border: 1px dashed transparent; 186 | transition: border-color 0.5s cubic-bezier(0.23, 1, 0.32, 1); 187 | 188 | & .ico { 189 | width: 40px; 190 | min-width: 40px; 191 | height: 26px; 192 | background-color: #fafafa; 193 | background-repeat: no-repeat; 194 | background-position: center; 195 | background-size: cover; 196 | border-radius: 1em; 197 | } 198 | } 199 | 200 | & .merge { 201 | width: calc(100% - 4em); 202 | } 203 | 204 | & .name { 205 | font-size: 0.9em; 206 | white-space: nowrap; 207 | overflow: hidden; 208 | text-overflow: ellipsis; 209 | font-weight: 700; 210 | } 211 | 212 | & .count { 213 | font-size: 0.75em; 214 | font-weight: 700; 215 | white-space: nowrap; 216 | overflow: hidden; 217 | text-overflow: ellipsis; 218 | } 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /public/styles/Update.postcss: -------------------------------------------------------------------------------- 1 | :root { 2 | --green-color: #31bc86; 3 | --grey-color: #8c99a7; 4 | --blue-color: #5396d8; 5 | } 6 | 7 | .update-notify { 8 | position: fixed; 9 | width: 100%; 10 | height: 100%; 11 | display: flex; 12 | flex-flow: column nowrap; 13 | align-items: center; 14 | left: 0; 15 | top: 0; 16 | padding: 2em; 17 | overflow: auto; 18 | opacity: 1; 19 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1); 20 | background-color: #fff; 21 | 22 | &.closed { 23 | opacity: 0; 24 | visibility: hidden; 25 | } 26 | 27 | &.checking { 28 | width: 100%; 29 | height: 100%; 30 | left: 0; 31 | top: 0; 32 | 33 | & .lds-ripple { 34 | opacity: 1; 35 | } 36 | } 37 | 38 | &.downloading { 39 | width: 400px; 40 | height: 100px; 41 | left: calc(50% - 200px); 42 | top: calc(50% - 50px); 43 | overflow: hidden; 44 | border-radius: 0.75em; 45 | 46 | & .update-container { 47 | opacity: 0; 48 | } 49 | } 50 | 51 | & .lds-ripple { 52 | opacity: 0; 53 | position: fixed; 54 | width: 58px; 55 | height: 58px; 56 | left: calc(50% - 29px); 57 | top: calc(50% - 29px); 58 | border-radius: 100%; 59 | 60 | & div { 61 | position: absolute; 62 | border: 2px dashed color-mod(var(--green-color) alpha(50%)); 63 | opacity: 1; 64 | border-radius: 50%; 65 | animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite; 66 | 67 | &:nth-child(2) { 68 | animation-delay: -0.5s; 69 | } 70 | } 71 | } 72 | 73 | & .update-container { 74 | max-width: 1600px; 75 | width: 100%; 76 | border-radius: 0.75em; 77 | transform: translateY(0); 78 | opacity: 1; 79 | transition: all 0.7s cubic-bezier(0.23, 1, 0.32, 1); 80 | animation-name: fade-one; 81 | animation-duration: 0.7s; 82 | animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1); 83 | 84 | & .section-name { 85 | display: inline-flex; 86 | font-weight: 100; 87 | font-size: 1em; 88 | color: color-mod(var(--green-color) alpha(75%)); 89 | border: 1px dashed color-mod(var(--green-color) alpha(50%)); 90 | padding: 0.25em 0.75em; 91 | border-radius: 1.25em; 92 | margin-bottom: 2em; 93 | } 94 | 95 | & .update-description { 96 | color: color-mod(var(--green-color) alpha(50%)); 97 | margin-bottom: 4em; 98 | } 99 | 100 | & .release-notes { 101 | color: var(--grey-color); 102 | border-top: 1px dashed color-mod(var(--green-color) alpha(10%)); 103 | border-bottom: 1px dashed color-mod(var(--green-color) alpha(10%)); 104 | padding-top: 2em; 105 | margin-bottom: 2em; 106 | 107 | & .note { 108 | margin-bottom: 2em; 109 | 110 | & .version { 111 | display: inline-flex; 112 | color: #fff; 113 | padding: 0.25em 0.75em; 114 | border-radius: 1.25em; 115 | background-color: color-mod(var(--green-color) alpha(75%)); 116 | } 117 | } 118 | } 119 | 120 | & .downloads { 121 | display: flex; 122 | flex-flow: column wrap; 123 | border-top: 1px dashed color-mod(var(--green-color) alpha(10%)); 124 | padding-top: 2em; 125 | margin-bottom: 2em; 126 | 127 | & a { 128 | color: color-mod(var(--green-color) alpha(65%)); 129 | text-decoration: none; 130 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1); 131 | margin: 0.15em 0; 132 | white-space: nowrap; 133 | overflow: hidden; 134 | text-overflow: ellipsis; 135 | 136 | &:hover { 137 | color: var(--green-color); 138 | } 139 | 140 | & span.size { 141 | margin-right: 0.5em; 142 | color: var(--grey-color); 143 | font-size: 0.85em; 144 | text-decoration: none; 145 | } 146 | } 147 | 148 | & p { 149 | color: var(--grey-color); 150 | font-weight: 700; 151 | 152 | &:first-child { 153 | margin-top: 0; 154 | } 155 | } 156 | } 157 | } 158 | } 159 | 160 | .download-progress { 161 | position: fixed; 162 | left: calc(50% - 200px + 2em); 163 | top: calc(50% - 5px); 164 | width: calc(400px - 4em); 165 | height: 10px; 166 | overflow: hidden; 167 | background-color: color-mod(var(--green-color) alpha(15%)); 168 | border-radius: 0.25em; 169 | } 170 | 171 | .download-progress-bar { 172 | position: absolute; 173 | background-color: rgba(49, 188, 135, 0.75); 174 | height: 100%; 175 | border-radius: 0.25em; 176 | transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1); 177 | } 178 | 179 | @keyframes lds-ripple { 180 | 0% { 181 | top: 28px; 182 | left: 28px; 183 | width: 0; 184 | height: 0; 185 | opacity: 1; 186 | } 187 | 100% { 188 | top: -1px; 189 | left: -1px; 190 | width: 58px; 191 | height: 58px; 192 | opacity: 0; 193 | } 194 | } 195 | 196 | @keyframes fade-one { 197 | from { 198 | opacity: 0; 199 | transform: translateY(10%); 200 | } 201 | to { 202 | opacity: 1; 203 | transform: translateY(0); 204 | } 205 | } -------------------------------------------------------------------------------- /src/actions/MainActions.js: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs'; 2 | import { remote } from 'electron'; 3 | import { showResult } from './ResultsActions'; 4 | import util from 'util'; 5 | 6 | const { dialog } = remote; 7 | const readFilePromisify = util.promisify(readFile); 8 | 9 | export const openFile = () => async dispatch => { 10 | const [readPath] = dialog.showOpenDialog({ 11 | filters: [ 12 | { 13 | name: 'Text Files', 14 | extensions: ['txt'] 15 | } 16 | ] 17 | }); 18 | 19 | if (readPath) { 20 | dispatch(showResult(await readFilePromisify(readPath, 'utf8'))); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/actions/ResultsActions.js: -------------------------------------------------------------------------------- 1 | import { sort } from 'js-flock'; 2 | import { writeFile } from 'fs'; 3 | import { lookup, codes } from '../core/country'; 4 | import { uniq } from '../misc/other'; 5 | import { remote } from 'electron'; 6 | import { RESULTS_SHOW, RESULTS_TOGGLE_COUNTRY, CLOSE } from '../constants/ActionTypes'; 7 | 8 | const { dialog } = remote; 9 | 10 | const findProxies = text => { 11 | try { 12 | return uniq(text.match(new RegExp('[1-2]?[0-9]{1,3}[.][1-2]?[0-9]{1,3}[.][1-2]?[0-9]{1,3}[.][1-2]?[0-9]{1,3}[:][1-9]?[0-9]{1,5}', 'g'))); 13 | } catch (error) { 14 | throw new Error('No proxies found!'); 15 | } 16 | }; 17 | 18 | export const showResult = text => dispatch => { 19 | try { 20 | const res = []; 21 | const countries = {}; 22 | const proxies = findProxies(text); 23 | 24 | proxies.forEach(item => { 25 | const [ip] = item.split(':'); 26 | const code = lookup(ip); 27 | 28 | if (countries[code] == undefined) { 29 | countries[code] = { 30 | code, 31 | ...codes[code], 32 | items: [item] 33 | }; 34 | } else { 35 | countries[code].items.push(item); 36 | } 37 | }); 38 | 39 | Object.keys(countries).forEach(item => { 40 | res.push({ 41 | ...countries[item], 42 | active: true 43 | }); 44 | }); 45 | 46 | dispatch({ 47 | type: RESULTS_SHOW, 48 | proxiesByCountries: sort(res).desc(item => item.items.length) 49 | }); 50 | } catch (error) { 51 | alert(error); 52 | } 53 | }; 54 | 55 | export const toggleCountry = (code, state, all) => ({ 56 | type: RESULTS_TOGGLE_COUNTRY, 57 | code, 58 | state, 59 | all 60 | }); 61 | 62 | export const save = () => (dispatch, getState) => { 63 | const savePath = dialog.showSaveDialog({ 64 | filters: [ 65 | { 66 | name: 'Text Files', 67 | extensions: ['txt'] 68 | } 69 | ] 70 | }); 71 | 72 | if (savePath) { 73 | const { 74 | results: { proxiesByCountries } 75 | } = getState(); 76 | 77 | const results = proxiesByCountries 78 | .filter(item => item.active) 79 | .map(item => item.items) 80 | .flat(); 81 | 82 | writeFile(savePath, results.join('\r\n'), () => null); 83 | } 84 | }; 85 | 86 | export const close = () => ({ 87 | type: CLOSE 88 | }); 89 | -------------------------------------------------------------------------------- /src/actions/UpdateActions.js: -------------------------------------------------------------------------------- 1 | import request from 'request'; 2 | import progress from 'request-progress'; 3 | import { remote, shell } from 'electron'; 4 | import { getLatestVersionInfo } from '../core/updater'; 5 | import { createWriteStream } from 'fs'; 6 | import { wait } from '../misc/other'; 7 | import { UPDATE_CHANGE_STATE, UPDATE_UP_DOWNLOAD_PROGRESS, UPDATE_CLOSE } from '../constants/ActionTypes'; 8 | 9 | const { dialog, getCurrentWindow } = remote; 10 | 11 | export const checkAtAvailable = () => async dispatch => { 12 | const details = await getLatestVersionInfo(); 13 | 14 | await wait(500); 15 | 16 | if (details) { 17 | dispatch( 18 | changeUpdateState({ 19 | isAvailable: true, 20 | isChecking: false, 21 | info: details 22 | }) 23 | ); 24 | } else { 25 | dispatch( 26 | changeUpdateState({ 27 | active: false, 28 | isChecking: false 29 | }) 30 | ); 31 | } 32 | }; 33 | 34 | const changeUpdateState = nextState => ({ 35 | type: UPDATE_CHANGE_STATE, 36 | nextState 37 | }); 38 | 39 | export const close = () => ({ 40 | type: UPDATE_CLOSE 41 | }); 42 | 43 | const upDownloadProgress = percent => ({ 44 | type: UPDATE_UP_DOWNLOAD_PROGRESS, 45 | percent 46 | }); 47 | 48 | export const download = e => async dispatch => { 49 | e.preventDefault(); 50 | 51 | let savePath = dialog.showSaveDialog({ 52 | defaultPath: e.target.title, 53 | filters: [ 54 | { 55 | name: '.rar, .exe, .zip, .7z', 56 | extensions: ['rar', 'exe', 'zip', '7z'] 57 | } 58 | ] 59 | }); 60 | 61 | if (!savePath) return; 62 | 63 | dispatch(changeUpdateState({ onDownloading: true })); 64 | 65 | progress(request(e.target.href), { 66 | throttle: 100 67 | }) 68 | .on('progress', state => { 69 | dispatch(upDownloadProgress(state.percent * 100)); 70 | }) 71 | .on('end', () => { 72 | shell.showItemInFolder(savePath); 73 | getCurrentWindow().close(); 74 | }) 75 | .pipe(createWriteStream(savePath)); 76 | }; -------------------------------------------------------------------------------- /src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shell } from 'electron'; 3 | import { currentVersion } from '../core/updater'; 4 | 5 | import '../../public/styles/Footer.postcss'; 6 | 7 | const openLink = e => { 8 | e.preventDefault(); 9 | shell.openExternal(e.currentTarget.href || e.currentTarget.getAttribute('xlink:href')); 10 | }; 11 | 12 | const Footer = () => ( 13 |
14 |
15 | 16 | 17 | 18 | 19 | assnctr 20 | 21 | 22 | openproxy.space 23 | 24 | Unfx proxy to country 25 | v{currentVersion} 26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |
51 | ); 52 | 53 | export default Footer; -------------------------------------------------------------------------------- /src/components/ResultsCountryItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { splitByKK } from '../misc/text'; 3 | 4 | export default class ResultsCountryItem extends React.PureComponent { 5 | toggle = () => { 6 | const { toggleCountry, code, active } = this.props; 7 | toggleCountry(code, !active); 8 | }; 9 | 10 | toggleAll = () => { 11 | const { toggleCountry, code, active } = this.props; 12 | toggleCountry(code, !active, true); 13 | }; 14 | 15 | render = () => { 16 | const { active, flag, name, items } = this.props; 17 | 18 | return ( 19 |
20 |
21 |
22 |
23 |
24 |
{name}
25 |
Proxies: {splitByKK(items.length)}
26 |
27 |
28 | ); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Root.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Main from '../containers/Main'; 3 | import Update from '../containers/Update'; 4 | import Results from '../containers/Results'; 5 | 6 | const Root = () => ( 7 |
8 |
9 | 10 | 11 |
12 | ); 13 | 14 | export default Root; 15 | -------------------------------------------------------------------------------- /src/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | //results 2 | export const RESULTS_SHOW = 'RESULTS_SHOW'; 3 | export const RESULTS_TOGGLE_COUNTRY = 'RESULTS_TOGGLE_COUNTRY'; 4 | export const CLOSE = 'CLOSE'; 5 | 6 | //update 7 | export const UPDATE_CHANGE_STATE = 'UPDATE_CHANGE_STATE'; 8 | export const UPDATE_UP_DOWNLOAD_PROGRESS = 'UPDATE_UP_DOWNLOAD_PROGRESS'; 9 | export const UPDATE_CLOSE = 'UPDATE_CLOSE'; 10 | -------------------------------------------------------------------------------- /src/containers/Main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { openFile } from '../actions/MainActions'; 4 | 5 | import '../../public/styles/Main.postcss'; 6 | import '../../public/styles/Elements.postcss'; 7 | 8 | const Main = ({ openFile }) => ( 9 |
10 |
11 | 12 |
13 |
14 | ); 15 | 16 | export default connect( 17 | null, 18 | { openFile } 19 | )(Main); 20 | -------------------------------------------------------------------------------- /src/containers/Results.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { splitByKK } from '../misc/text'; 4 | import { save, close, toggleCountry } from '../actions/ResultsActions'; 5 | import ResultsCountryItem from '../components/ResultsCountryItem'; 6 | import Footer from '../components/Footer'; 7 | 8 | import '../../public/styles/Results.postcss'; 9 | import '../../public/styles/Icons.postcss'; 10 | 11 | const Results = ({ active, proxiesByCountries, save, close, toggleCountry }) => { 12 | const activeCountries = proxiesByCountries.filter(item => item.active); 13 | const selectedProxiesCount = activeCountries.reduce((prev, curr) => prev + curr.items.length, 0); 14 | 15 | return ( 16 |
17 |
18 |
19 |
0 ? 'has-items' : 'no-items'}`}> 20 | 21 | Selected: {activeCountries.length} of {proxiesByCountries.length} 22 | 23 | Proxies: {splitByKK(selectedProxiesCount)} 24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 |
40 | {proxiesByCountries.map(country => ( 41 | 42 | ))} 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | Double click select or deselect all 80 | 81 |
82 |
83 |
84 |
85 | ); 86 | }; 87 | 88 | const mapStateToProps = state => ({ 89 | ...state.results 90 | }); 91 | 92 | const mapDispatchToProps = { 93 | save, 94 | close, 95 | toggleCountry 96 | }; 97 | 98 | export default connect( 99 | mapStateToProps, 100 | mapDispatchToProps 101 | )(Results); 102 | -------------------------------------------------------------------------------- /src/containers/Update.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import { connect } from 'react-redux'; 4 | import { checkAtAvailable, close, download } from '../actions/UpdateActions'; 5 | import { bytesToSize } from '../misc/text'; 6 | 7 | import '../../public/styles/Update.postcss'; 8 | 9 | class Update extends React.PureComponent { 10 | componentWillMount = () => { 11 | const { checkAtAvailable } = this.props; 12 | checkAtAvailable(); 13 | }; 14 | 15 | render = () => { 16 | const { active, isAvailable, isChecking, onDownloading, downloadProgress, info, close, download } = this.props; 17 | const progress = { 18 | width: downloadProgress + '%' 19 | }; 20 | 21 | return ( 22 |
23 |
24 |
25 |
26 |
27 | {isAvailable && ( 28 |
29 | Update is available 30 |
31 | {info.releaseNotes.map(note => ( 32 |
33 | Release Notes for v{note.version} 34 | 35 |
36 | ))} 37 |
38 | Downloads 39 |
40 |

Windows

41 | {info.assets.windows.map(asset => ( 42 | 43 | {bytesToSize(asset.size)} 44 | {asset.name} 45 | 46 | ))} 47 |

Linux

48 | {info.assets.linux.map(asset => ( 49 | 50 | {bytesToSize(asset.size)} 51 | {asset.name} 52 | 53 | ))} 54 |
55 | 56 |
57 | )} 58 | {onDownloading && ( 59 |
60 |
61 |
62 | )} 63 |
64 | ); 65 | }; 66 | } 67 | 68 | const mapStateToProps = state => ({ 69 | ...state.update 70 | }); 71 | 72 | const mapDispatchToProps = { 73 | checkAtAvailable, 74 | close, 75 | download 76 | }; 77 | 78 | export default connect( 79 | mapStateToProps, 80 | mapDispatchToProps 81 | )(Update); 82 | -------------------------------------------------------------------------------- /src/core/country.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import MMDBReader from 'mmdb-reader'; 3 | 4 | const pathToMmdb = process.env.NODE_ENV !== 'production' ? './files/GeoLite2-Country.mmdb' : './resources/files/GeoLite2-Country.mmdb'; 5 | 6 | const reader = new MMDBReader(path.resolve(pathToMmdb)); 7 | 8 | export const codes = { 9 | AF: { flag: 'afghanistan', name: 'Afghanistan' }, 10 | AL: { flag: 'albania', name: 'Albania' }, 11 | DZ: { flag: 'algeria', name: 'Algeria' }, 12 | DS: { flag: 'american-samoa', name: 'American Samoa' }, 13 | AD: { flag: 'andorra', name: 'Andorra' }, 14 | AO: { flag: 'angola', name: 'Angola' }, 15 | AI: { flag: 'anguilla', name: 'Anguilla' }, 16 | AQ: { flag: 'antarctica', name: 'Antarctica' }, 17 | AG: { flag: 'antigua-and-barbuda', name: 'Antigua and Barbuda' }, 18 | AR: { flag: 'argentina', name: 'Argentina' }, 19 | AM: { flag: 'armenia', name: 'Armenia' }, 20 | AW: { flag: 'aruba', name: 'Aruba' }, 21 | AU: { flag: 'australia', name: 'Australia' }, 22 | AT: { flag: 'austria', name: 'Austria' }, 23 | AZ: { flag: 'azerbaijan', name: 'Azerbaijan' }, 24 | BS: { flag: 'bahamas', name: 'Bahamas' }, 25 | BH: { flag: 'bahrain', name: 'Bahrain' }, 26 | BD: { flag: 'bangladesh', name: 'Bangladesh' }, 27 | BB: { flag: 'barbados', name: 'Barbados' }, 28 | BY: { flag: 'belarus', name: 'Belarus' }, 29 | BE: { flag: 'belgium', name: 'Belgium' }, 30 | BZ: { flag: 'belize', name: 'Belize' }, 31 | BJ: { flag: 'benin', name: 'Benin' }, 32 | BM: { flag: 'bermuda', name: 'Bermuda' }, 33 | BT: { flag: 'bhutan', name: 'Bhutan' }, 34 | BO: { flag: 'bolivia', name: 'Bolivia' }, 35 | BA: { flag: 'bosnia-and-herzegovina', name: 'Bosnia and Herzegovina' }, 36 | BW: { flag: 'botswana', name: 'Botswana' }, 37 | BV: { flag: 'bouvet-island', name: 'Bouvet Island' }, 38 | BR: { flag: 'brazil', name: 'Brazil' }, 39 | IO: { flag: 'british-indian-ocean-territory', name: 'British Indian Ocean Territory' }, 40 | BN: { flag: 'brunei-darussalam', name: 'Brunei Darussalam' }, 41 | BG: { flag: 'bulgaria', name: 'Bulgaria' }, 42 | BF: { flag: 'burkina-faso', name: 'Burkina Faso' }, 43 | BI: { flag: 'burundi', name: 'Burundi' }, 44 | KH: { flag: 'cambodia', name: 'Cambodia' }, 45 | CM: { flag: 'cameroon', name: 'Cameroon' }, 46 | CA: { flag: 'canada', name: 'Canada' }, 47 | CV: { flag: 'cape-verde', name: 'Cape Verde' }, 48 | KY: { flag: 'cayman-islands', name: 'Cayman Islands' }, 49 | CF: { flag: 'central-african-republic', name: 'Central African Republic' }, 50 | TD: { flag: 'chad', name: 'Chad' }, 51 | CL: { flag: 'chile', name: 'Chile' }, 52 | CN: { flag: 'china', name: 'China' }, 53 | CX: { flag: 'christmas-island', name: 'Christmas Island' }, 54 | CC: { flag: 'cocos-keeling-islands', name: 'Cocos (Keeling) Islands' }, 55 | CO: { flag: 'colombia', name: 'Colombia' }, 56 | KM: { flag: 'comoros', name: 'Comoros' }, 57 | CG: { flag: 'congo', name: 'Congo' }, 58 | CK: { flag: 'cook-islands', name: 'Cook Islands' }, 59 | CR: { flag: 'costa-rica', name: 'Costa Rica' }, 60 | HR: { flag: 'croatia-hrvatska', name: 'Croatia (Hrvatska)' }, 61 | CU: { flag: 'cuba', name: 'Cuba' }, 62 | CY: { flag: 'cyprus', name: 'Cyprus' }, 63 | CZ: { flag: 'czech-republic', name: 'Czech Republic' }, 64 | DK: { flag: 'denmark', name: 'Denmark' }, 65 | DJ: { flag: 'djibouti', name: 'Djibouti' }, 66 | DM: { flag: 'dominica', name: 'Dominica' }, 67 | DO: { flag: 'dominican-republic', name: 'Dominican Republic' }, 68 | TP: { flag: 'east-timor', name: 'East Timor' }, 69 | EC: { flag: 'ecuador', name: 'Ecuador' }, 70 | EG: { flag: 'egypt', name: 'Egypt' }, 71 | SV: { flag: 'el-salvador', name: 'El Salvador' }, 72 | GQ: { flag: 'equatorial-guinea', name: 'Equatorial Guinea' }, 73 | ER: { flag: 'eritrea', name: 'Eritrea' }, 74 | EE: { flag: 'estonia', name: 'Estonia' }, 75 | ET: { flag: 'ethiopia', name: 'Ethiopia' }, 76 | FK: { flag: 'falkland-islands-malvinas', name: 'Falkland Islands (Malvinas)' }, 77 | FO: { flag: 'faroe-islands', name: 'Faroe Islands' }, 78 | FJ: { flag: 'fiji', name: 'Fiji' }, 79 | FI: { flag: 'finland', name: 'Finland' }, 80 | FR: { flag: 'france', name: 'France' }, 81 | FX: { flag: 'france-metropolitan', name: 'France, Metropolitan' }, 82 | GF: { flag: 'french-guiana', name: 'French Guiana' }, 83 | PF: { flag: 'french-polynesia', name: 'French Polynesia' }, 84 | TF: { flag: 'french-southern-territories', name: 'French Southern Territories' }, 85 | GA: { flag: 'gabon', name: 'Gabon' }, 86 | GM: { flag: 'gambia', name: 'Gambia' }, 87 | GE: { flag: 'georgia', name: 'Georgia' }, 88 | DE: { flag: 'germany', name: 'Germany' }, 89 | GH: { flag: 'ghana', name: 'Ghana' }, 90 | GI: { flag: 'gibraltar', name: 'Gibraltar' }, 91 | GK: { flag: 'guernsey', name: 'Guernsey' }, 92 | GR: { flag: 'greece', name: 'Greece' }, 93 | GL: { flag: 'greenland', name: 'Greenland' }, 94 | GD: { flag: 'grenada', name: 'Grenada' }, 95 | GP: { flag: 'guadeloupe', name: 'Guadeloupe' }, 96 | GU: { flag: 'guam', name: 'Guam' }, 97 | GT: { flag: 'guatemala', name: 'Guatemala' }, 98 | GN: { flag: 'guinea', name: 'Guinea' }, 99 | GW: { flag: 'guinea-bissau', name: 'Guinea-Bissau' }, 100 | GY: { flag: 'guyana', name: 'Guyana' }, 101 | HT: { flag: 'haiti', name: 'Haiti' }, 102 | HM: { flag: 'heard-and-mc-donald-islands', name: 'Heard and Mc Donald Islands' }, 103 | HN: { flag: 'honduras', name: 'Honduras' }, 104 | HK: { flag: 'hong-kong', name: 'Hong Kong' }, 105 | HU: { flag: 'hungary', name: 'Hungary' }, 106 | IS: { flag: 'iceland', name: 'Iceland' }, 107 | IN: { flag: 'india', name: 'India' }, 108 | IM: { flag: 'isle-of-man', name: 'Isle of Man' }, 109 | ID: { flag: 'indonesia', name: 'Indonesia' }, 110 | IR: { flag: 'iran', name: 'Iran' }, 111 | IQ: { flag: 'iraq', name: 'Iraq' }, 112 | IE: { flag: 'ireland', name: 'Ireland' }, 113 | IL: { flag: 'israel', name: 'Israel' }, 114 | IT: { flag: 'italy', name: 'Italy' }, 115 | CI: { flag: 'ivory-coast', name: 'Ivory Coast' }, 116 | JE: { flag: 'jersey', name: 'Jersey' }, 117 | JM: { flag: 'jamaica', name: 'Jamaica' }, 118 | JP: { flag: 'japan', name: 'Japan' }, 119 | JO: { flag: 'jordan', name: 'Jordan' }, 120 | KZ: { flag: 'kazakhstan', name: 'Kazakhstan' }, 121 | KE: { flag: 'kenya', name: 'Kenya' }, 122 | KI: { flag: 'kiribati', name: 'Kiribati' }, 123 | KP: { flag: 'korea-democratic-peoples-republic-of', name: "Korea, Democratic People's Republic of" }, 124 | KR: { flag: 'korea', name: 'Korea' }, 125 | XK: { flag: 'kosovo', name: 'Kosovo' }, 126 | KW: { flag: 'kuwait', name: 'Kuwait' }, 127 | KG: { flag: 'kyrgyzstan', name: 'Kyrgyzstan' }, 128 | LA: { flag: 'laos', name: 'Laos' }, 129 | LV: { flag: 'latvia', name: 'Latvia' }, 130 | LB: { flag: 'lebanon', name: 'Lebanon' }, 131 | LS: { flag: 'lesotho', name: 'Lesotho' }, 132 | LR: { flag: 'liberia', name: 'Liberia' }, 133 | LY: { flag: 'libyan-arab-jamahiriya', name: 'Libyan Arab Jamahiriya' }, 134 | LI: { flag: 'liechtenstein', name: 'Liechtenstein' }, 135 | LT: { flag: 'lithuania', name: 'Lithuania' }, 136 | LU: { flag: 'luxembourg', name: 'Luxembourg' }, 137 | MO: { flag: 'macau', name: 'Macau' }, 138 | MK: { flag: 'macedonia', name: 'Macedonia' }, 139 | MG: { flag: 'madagascar', name: 'Madagascar' }, 140 | MW: { flag: 'malawi', name: 'Malawi' }, 141 | MY: { flag: 'malaysia', name: 'Malaysia' }, 142 | MV: { flag: 'maldives', name: 'Maldives' }, 143 | ML: { flag: 'mali', name: 'Mali' }, 144 | MT: { flag: 'malta', name: 'Malta' }, 145 | MH: { flag: 'marshall-islands', name: 'Marshall Islands' }, 146 | MQ: { flag: 'martinique', name: 'Martinique' }, 147 | MR: { flag: 'mauritania', name: 'Mauritania' }, 148 | MU: { flag: 'mauritius', name: 'Mauritius' }, 149 | TY: { flag: 'mayotte', name: 'Mayotte' }, 150 | MX: { flag: 'mexico', name: 'Mexico' }, 151 | FM: { flag: 'micronesia-federated-states-of', name: 'Micronesia, Federated States of' }, 152 | MD: { flag: 'moldova', name: 'Moldova' }, 153 | MC: { flag: 'monaco', name: 'Monaco' }, 154 | MN: { flag: 'mongolia', name: 'Mongolia' }, 155 | ME: { flag: 'montenegro', name: 'Montenegro' }, 156 | MS: { flag: 'montserrat', name: 'Montserrat' }, 157 | MA: { flag: 'morocco', name: 'Morocco' }, 158 | MZ: { flag: 'mozambique', name: 'Mozambique' }, 159 | MM: { flag: 'myanmar', name: 'Myanmar' }, 160 | NA: { flag: 'namibia', name: 'Namibia' }, 161 | NR: { flag: 'nauru', name: 'Nauru' }, 162 | NP: { flag: 'nepal', name: 'Nepal' }, 163 | NL: { flag: 'netherlands', name: 'Netherlands' }, 164 | AN: { flag: 'netherlands-antilles', name: 'Netherlands Antilles' }, 165 | NC: { flag: 'new-caledonia', name: 'New Caledonia' }, 166 | NZ: { flag: 'new-zealand', name: 'New Zealand' }, 167 | NI: { flag: 'nicaragua', name: 'Nicaragua' }, 168 | NE: { flag: 'niger', name: 'Niger' }, 169 | NG: { flag: 'nigeria', name: 'Nigeria' }, 170 | NU: { flag: 'niue', name: 'Niue' }, 171 | NF: { flag: 'norfolk-island', name: 'Norfolk Island' }, 172 | MP: { flag: 'northern-mariana-islands', name: 'Northern Mariana Islands' }, 173 | NO: { flag: 'norway', name: 'Norway' }, 174 | OM: { flag: 'oman', name: 'Oman' }, 175 | PK: { flag: 'pakistan', name: 'Pakistan' }, 176 | PW: { flag: 'palau', name: 'Palau' }, 177 | PS: { flag: 'palestine', name: 'Palestine' }, 178 | PA: { flag: 'panama', name: 'Panama' }, 179 | PG: { flag: 'papua-new-guinea', name: 'Papua New Guinea' }, 180 | PY: { flag: 'paraguay', name: 'Paraguay' }, 181 | PE: { flag: 'peru', name: 'Peru' }, 182 | PH: { flag: 'philippines', name: 'Philippines' }, 183 | PN: { flag: 'pitcairn', name: 'Pitcairn' }, 184 | PL: { flag: 'poland', name: 'Poland' }, 185 | PT: { flag: 'portugal', name: 'Portugal' }, 186 | PR: { flag: 'puerto-rico', name: 'Puerto Rico' }, 187 | QA: { flag: 'qatar', name: 'Qatar' }, 188 | RE: { flag: 'reunion', name: 'Reunion' }, 189 | RO: { flag: 'romania', name: 'Romania' }, 190 | RU: { flag: 'russian-federation', name: 'Russian Federation' }, 191 | RW: { flag: 'rwanda', name: 'Rwanda' }, 192 | KN: { flag: 'saint-kitts-and-nevis', name: 'Saint Kitts and Nevis' }, 193 | LC: { flag: 'saint-lucia', name: 'Saint Lucia' }, 194 | VC: { flag: 'saint-vincent-and-the-grenadines', name: 'Saint Vincent and the Grenadines' }, 195 | WS: { flag: 'samoa', name: 'Samoa' }, 196 | SM: { flag: 'san-marino', name: 'San Marino' }, 197 | ST: { flag: 'sao-tome-and-principe', name: 'Sao Tome and Principe' }, 198 | SA: { flag: 'saudi-arabia', name: 'Saudi Arabia' }, 199 | SN: { flag: 'senegal', name: 'Senegal' }, 200 | RS: { flag: 'serbia', name: 'Serbia' }, 201 | SC: { flag: 'seychelles', name: 'Seychelles' }, 202 | SL: { flag: 'sierra-leone', name: 'Sierra Leone' }, 203 | SG: { flag: 'singapore', name: 'Singapore' }, 204 | SK: { flag: 'slovakia', name: 'Slovakia' }, 205 | SI: { flag: 'slovenia', name: 'Slovenia' }, 206 | SB: { flag: 'solomon-islands', name: 'Solomon Islands' }, 207 | SO: { flag: 'somalia', name: 'Somalia' }, 208 | ZA: { flag: 'south-africa', name: 'South Africa' }, 209 | GS: { flag: 'south-georgia-south-sandwich-islands', name: 'South Georgia South Sandwich Islands' }, 210 | ES: { flag: 'spain', name: 'Spain' }, 211 | LK: { flag: 'sri-lanka', name: 'Sri Lanka' }, 212 | SH: { flag: 'st-helena', name: 'St. Helena' }, 213 | PM: { flag: 'st-pierre-and-miquelon', name: 'St. Pierre and Miquelon' }, 214 | SD: { flag: 'sudan', name: 'Sudan' }, 215 | SR: { flag: 'suriname', name: 'Suriname' }, 216 | SJ: { flag: 'svalbard-and-jan-mayen-islands', name: 'Svalbard and Jan Mayen Islands' }, 217 | SZ: { flag: 'swaziland', name: 'Swaziland' }, 218 | SE: { flag: 'sweden', name: 'Sweden' }, 219 | CH: { flag: 'switzerland', name: 'Switzerland' }, 220 | SY: { flag: 'syria', name: 'Syria' }, 221 | TW: { flag: 'taiwan', name: 'Taiwan' }, 222 | TJ: { flag: 'tajikistan', name: 'Tajikistan' }, 223 | TZ: { flag: 'tanzania', name: 'Tanzania' }, 224 | TH: { flag: 'thailand', name: 'Thailand' }, 225 | TG: { flag: 'togo', name: 'Togo' }, 226 | TK: { flag: 'tokelau', name: 'Tokelau' }, 227 | TO: { flag: 'tonga', name: 'Tonga' }, 228 | TT: { flag: 'trinidad-and-tobago', name: 'Trinidad and Tobago' }, 229 | TN: { flag: 'tunisia', name: 'Tunisia' }, 230 | TR: { flag: 'turkey', name: 'Turkey' }, 231 | TM: { flag: 'turkmenistan', name: 'Turkmenistan' }, 232 | TC: { flag: 'turks-and-caicos-islands', name: 'Turks and Caicos Islands' }, 233 | TV: { flag: 'tuvalu', name: 'Tuvalu' }, 234 | UG: { flag: 'uganda', name: 'Uganda' }, 235 | UA: { flag: 'ukraine', name: 'Ukraine' }, 236 | AE: { flag: 'united-arab-emirates', name: 'United Arab Emirates' }, 237 | GB: { flag: 'united-kingdom', name: 'United Kingdom' }, 238 | US: { flag: 'united-states', name: 'United States' }, 239 | UM: { flag: 'united-states-minor-outlying-islands', name: 'United States minor outlying islands' }, 240 | UY: { flag: 'uruguay', name: 'Uruguay' }, 241 | UZ: { flag: 'uzbekistan', name: 'Uzbekistan' }, 242 | VU: { flag: 'vanuatu', name: 'Vanuatu' }, 243 | VA: { flag: 'vatican-city-state', name: 'Vatican City State' }, 244 | VE: { flag: 'venezuela', name: 'Venezuela' }, 245 | VN: { flag: 'vietnam', name: 'Vietnam' }, 246 | VG: { flag: 'virgin-islands-british', name: 'Virgin Islands (British)' }, 247 | VI: { flag: 'virgin-islands-us', name: 'Virgin Islands (U.S.)' }, 248 | WF: { flag: 'wallis-and-futuna-islands', name: 'Wallis and Futuna Islands' }, 249 | EH: { flag: 'western-sahara', name: 'Western Sahara' }, 250 | YE: { flag: 'yemen', name: 'Yemen' }, 251 | ZR: { flag: 'zaire', name: 'Zaire' }, 252 | ZM: { flag: 'zambia', name: 'Zambia' }, 253 | ZW: { flag: 'zimbabwe', name: 'Zimbabwe' }, 254 | AS: { flag: 'american-samoa', name: 'American Samoa' }, 255 | AX: { flag: 'aland-islands', name: 'Aland Islands' }, 256 | BQ: { flag: 'bonaire-sint-eustatius-saba', name: 'Bonaire, Sint Eustatius, Saba' }, 257 | CD: { flag: 'congo-the-democratic-republic-of-the', name: 'Congo The Democratic Republic of The' }, 258 | CW: { flag: 'curacao', name: 'Curacao' }, 259 | EU: { flag: 'european-union', name: 'European Union' }, 260 | GG: { flag: 'guernsey', name: 'Guernsey' }, 261 | MF: { flag: 'saint-martin', name: 'Saint Martin' }, 262 | SS: { flag: 'south-sudan', name: 'South Sudan' }, 263 | SX: { flag: 'sint-maarten', name: 'Sint Maarten' }, 264 | TL: { flag: 'timor-leste', name: 'Timor-leste' }, 265 | YT: { flag: 'mayotte', name: 'Mayotte' }, 266 | ZZ: { flag: 'unknown', name: 'Unknown' } 267 | }; 268 | 269 | export const lookup = ip => { 270 | try { 271 | return reader.lookup(ip).country.iso_code; 272 | } catch (error) { 273 | return 'ZZ'; 274 | } 275 | }; 276 | -------------------------------------------------------------------------------- /src/core/updater.js: -------------------------------------------------------------------------------- 1 | import rp from 'request-promise'; 2 | import { remote } from 'electron'; 3 | import { sort } from 'js-flock'; 4 | 5 | const { 6 | app: { getVersion } 7 | } = remote; 8 | 9 | export const currentVersion = getVersion(); 10 | 11 | export const getLatestVersionInfo = async () => { 12 | try { 13 | const releases = await rp.get({ 14 | url: 'https://api.github.com/repos/assnctr/unfx-proxy-to-country/releases', 15 | json: true, 16 | timeout: 5000, 17 | headers: { 18 | 'User-Agent': 'Unfx Version Lookup' 19 | } 20 | }); 21 | 22 | const [latest] = releases; 23 | const version = latest.tag_name.slice(1); 24 | 25 | if (version > currentVersion) { 26 | const releaseNotes = releases 27 | .filter(item => item.tag_name.slice(1) > currentVersion) 28 | .map(item => ({ 29 | version: item.tag_name.slice(1), 30 | body: item.body 31 | })); 32 | 33 | sort(latest.assets).desc(item => item.size); 34 | 35 | const assets = { 36 | windows: latest.assets.filter(asset => asset.name.match(/win|nsis/i)), 37 | linux: latest.assets.filter(asset => asset.name.match(/linux/i)) 38 | }; 39 | 40 | return { 41 | latestVersion: version, 42 | releaseNotes, 43 | assets 44 | }; 45 | } else { 46 | return false; 47 | } 48 | } catch (error) { 49 | return false; 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import url from 'url'; 3 | import { BrowserWindow, app } from 'electron'; 4 | import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer'; 5 | 6 | let window; 7 | 8 | const devWindow = () => { 9 | installExtension(REACT_DEVELOPER_TOOLS) 10 | .then(name => console.log('Added:', name)) 11 | .catch(err => console.log('Error:', err)); 12 | 13 | window = new BrowserWindow({ 14 | width: 1650, 15 | height: 980, 16 | show: false 17 | }); 18 | 19 | window.webContents.openDevTools(); 20 | }; 21 | 22 | const prodWindow = () => { 23 | window = new BrowserWindow({ 24 | minWidth: 1000, 25 | minHeight: 680, 26 | width: 1180, 27 | height: 752, 28 | show: false, 29 | resizable: true 30 | }); 31 | 32 | window.setMenu(null); 33 | }; 34 | 35 | const createWindow = () => { 36 | process.env.NODE_ENV !== 'production' ? devWindow() : prodWindow(); 37 | 38 | window.loadURL( 39 | process.env.NODE_ENV !== 'production' 40 | ? 'http://localhost:8080' 41 | : url.format({ 42 | pathname: path.join(__dirname, 'index.html'), 43 | protocol: 'file:', 44 | slashes: true 45 | }) 46 | ); 47 | 48 | window.on('ready-to-show', () => { 49 | window.show(); 50 | }); 51 | 52 | window.on('closed', () => { 53 | window = null; 54 | }); 55 | }; 56 | 57 | app.on('ready', createWindow); 58 | 59 | app.on('window-all-closed', () => { 60 | if (process.platform !== 'darwin') { 61 | app.quit(); 62 | } 63 | }); 64 | 65 | app.on('activate', () => { 66 | if (window === null) { 67 | createWindow(); 68 | } 69 | }); 70 | -------------------------------------------------------------------------------- /src/misc/other.js: -------------------------------------------------------------------------------- 1 | export const uniq = array => [...new Set([...array])]; 2 | 3 | export const wait = ms => { 4 | return new Promise(resolve => setTimeout(resolve, ms)); 5 | }; 6 | -------------------------------------------------------------------------------- /src/misc/text.js: -------------------------------------------------------------------------------- 1 | export const splitByKK = content => content.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); 2 | 3 | export const bytesToSize = bytes => { 4 | if (bytes == 0) return '0 B'; 5 | 6 | const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; 7 | const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); 8 | return `${Math.round(bytes / Math.pow(1024, i))} ${sizes[i]}`; 9 | }; 10 | -------------------------------------------------------------------------------- /src/renderer.dev.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Root from './components/Root'; 4 | import { AppContainer } from 'react-hot-loader'; 5 | import { Provider } from 'react-redux'; 6 | import store from './store/index'; 7 | 8 | const root = document.getElementById('root'); 9 | 10 | const render = () => { 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | , 17 | root 18 | ); 19 | }; 20 | 21 | render(); 22 | 23 | if (module.hot) { 24 | module.hot.accept('./components/Root', render); 25 | } 26 | -------------------------------------------------------------------------------- /src/renderer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Root from './components/Root'; 4 | import { Provider } from 'react-redux'; 5 | import store from './store/index'; 6 | 7 | const root = document.getElementById('root'); 8 | 9 | const render = () => { 10 | ReactDOM.render( 11 | 12 | 13 | , 14 | root 15 | ); 16 | }; 17 | 18 | render(); 19 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import rootReducer from './reducers/'; 2 | import { createStore, applyMiddleware } from 'redux'; 3 | import thunkMiddleware from 'redux-thunk'; 4 | 5 | const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore); 6 | const store = createStoreWithMiddleware(rootReducer); 7 | 8 | export default store; 9 | -------------------------------------------------------------------------------- /src/store/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import results from './results'; 3 | import update from './update'; 4 | 5 | const rootReducer = combineReducers({ 6 | results, 7 | update 8 | }); 9 | 10 | export default rootReducer; 11 | -------------------------------------------------------------------------------- /src/store/reducers/results.js: -------------------------------------------------------------------------------- 1 | import { RESULTS_SHOW, RESULTS_TOGGLE_COUNTRY, CLOSE } from '../../constants/ActionTypes'; 2 | 3 | const initial = { 4 | active: false, 5 | proxiesByCountries: [] 6 | }; 7 | 8 | const results = (state = initial, action) => { 9 | switch (action.type) { 10 | case RESULTS_SHOW: 11 | return { 12 | active: true, 13 | proxiesByCountries: action.proxiesByCountries 14 | }; 15 | case RESULTS_TOGGLE_COUNTRY: 16 | if (action.all) { 17 | return { 18 | ...state, 19 | proxiesByCountries: state.proxiesByCountries.map(item => { 20 | return { 21 | ...item, 22 | active: action.state 23 | }; 24 | }) 25 | }; 26 | } 27 | 28 | return { 29 | ...state, 30 | proxiesByCountries: state.proxiesByCountries.map(item => { 31 | if (item.code == action.code) { 32 | return { 33 | ...item, 34 | active: action.state 35 | }; 36 | } 37 | 38 | return item; 39 | }) 40 | }; 41 | case CLOSE: 42 | return initial; 43 | default: 44 | return state; 45 | } 46 | }; 47 | 48 | export default results; 49 | -------------------------------------------------------------------------------- /src/store/reducers/update.js: -------------------------------------------------------------------------------- 1 | import { UPDATE_CHANGE_STATE, UPDATE_UP_DOWNLOAD_PROGRESS, UPDATE_CLOSE } from '../../constants/ActionTypes'; 2 | 3 | const initialState = { 4 | active: true, 5 | isAvailable: false, 6 | isChecking: true, 7 | onDownloading: false, 8 | downloadProgress: 0, 9 | info: null 10 | }; 11 | 12 | const update = (state = initialState, action) => { 13 | switch (action.type) { 14 | case UPDATE_CHANGE_STATE: 15 | return { 16 | ...state, 17 | ...action.nextState 18 | }; 19 | case UPDATE_UP_DOWNLOAD_PROGRESS: 20 | return { 21 | ...state, 22 | downloadProgress: action.percent 23 | }; 24 | case UPDATE_CLOSE: 25 | return { 26 | ...state, 27 | active: false 28 | }; 29 | default: 30 | return state; 31 | } 32 | }; 33 | 34 | export default update; 35 | -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | const getBabelLoader = () => { 5 | const baseOptions = JSON.parse(fs.readFileSync(path.join(__dirname, '.babelrc'), 'utf-8')); 6 | const options = { 7 | ...baseOptions, 8 | presets: baseOptions.presets.map(preset => (preset === '@babel/env' ? ['@babel/env', { modules: false }] : preset)), 9 | babelrc: false 10 | }; 11 | 12 | return { 13 | loader: 'babel-loader', 14 | options 15 | }; 16 | }; 17 | 18 | export default { 19 | resolve: { 20 | extensions: ['.js', '.jsx', '.json'], 21 | modules: [path.join(__dirname, 'src'), 'node_modules'] 22 | }, 23 | output: { 24 | path: path.join(__dirname, 'public'), 25 | filename: '[name].js', 26 | publicPath: '/' 27 | }, 28 | node: { 29 | __dirname: false, 30 | __filename: false 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.jsx?$/, 36 | include: path.join(__dirname, 'src'), 37 | loader: getBabelLoader() 38 | }, 39 | { 40 | test: /\.(png|woff|woff2|eot|ttf|svg)$/, 41 | loader: 'url-loader', 42 | options: { 43 | name: "[path][name].[ext]", 44 | } 45 | }, 46 | { 47 | test: /\.(postcss|css)?$/, 48 | use: ['style-loader', { 49 | loader: 'css-loader', 50 | options: { 51 | importLoaders: 1 52 | } 53 | }, 'postcss-loader'] 54 | } 55 | ] 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /webpack.config.main.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import baseConfig from './webpack.config.base'; 3 | 4 | export default { 5 | ...baseConfig, 6 | entry: { 7 | main: path.join(__dirname, 'src/index') 8 | }, 9 | target: 'electron-main' 10 | }; 11 | -------------------------------------------------------------------------------- /webpack.config.renderer.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import baseConfig from './webpack.config.base'; 4 | 5 | export default { 6 | ...baseConfig, 7 | entry: { 8 | renderer: ['react-hot-loader/patch', path.join(__dirname, process.env.NODE_ENV === 'production' ? 'src/renderer' : 'src/renderer.dev')] 9 | }, 10 | devServer: { 11 | headers: { 12 | 'Access-Control-Allow-Origin': '*' 13 | }, 14 | contentBase: baseConfig.output.path, 15 | publicPath: baseConfig.output.publicPath, 16 | historyApiFallback: true, 17 | hotOnly: true 18 | }, 19 | ...(process.env.NODE_ENV === 'production' 20 | ? {} 21 | : { 22 | plugins: [new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin()] 23 | }), 24 | target: 'electron-renderer' 25 | }; 26 | --------------------------------------------------------------------------------