diff --git a/electron/process_manager.ts b/electron/process_manager.ts index c81a457..5e35c02 100644 --- a/electron/process_manager.ts +++ b/electron/process_manager.ts @@ -18,9 +18,9 @@ import { powerMonitor } from "electron"; import { checkUdpForwardingEnabled, isServerReachable } from "./connectivity"; import { ChildProcess, spawn } from "child_process"; import { pathToEmbeddedBinary, RemoteServer, pathToConfig } from "./utils"; -import { validateServerCredentials } from "../src/utils/connectivity"; import { SMART_DNS_ADDRESS } from "../src/constants"; import { BrowserWindow } from "electron"; +import { validateServerCredentials } from "../src/share"; export type Dns = | { diff --git a/electron/utils.ts b/electron/utils.ts index d017fb9..1af2458 100644 --- a/electron/utils.ts +++ b/electron/utils.ts @@ -10,7 +10,7 @@ import { SMART_DNS_WHITE_LIST_SERVERS } from "./constant"; import { AdditionalRoute } from "./main"; -import { lookupIp } from "../src/utils/helper"; +import { lookupIp } from "../src/share"; export const setMenu = (mainWindow: BrowserWindow) => { if (process.env.NODE_ENV === "development") { diff --git a/src/components/Cards/LatencyCard.tsx b/src/components/Cards/LatencyCard.tsx index 57bdc04..3df0393 100644 --- a/src/components/Cards/LatencyCard.tsx +++ b/src/components/Cards/LatencyCard.tsx @@ -5,12 +5,9 @@ import React, { useCallback, useEffect, useRef } from "react"; import { useSelector } from "react-redux"; import { AppState } from "../../reducers/rootReducer"; import { getActivatedServer } from "../Proxies/util"; -import { - checkServer, - checkDns, - validateServerCredentials -} from "../../utils/connectivity"; +import { checkServer, checkDns } from "../../utils/connectivity"; import { useAsync } from "../../hooks"; +import { validateServerCredentials } from "../../share"; //TODO: Remove repeated code export const LatencyCard = () => { diff --git a/src/share/index.ts b/src/share/index.ts new file mode 100644 index 0000000..ea9c43d --- /dev/null +++ b/src/share/index.ts @@ -0,0 +1,88 @@ +//The code is used by main and renderer process. +//TODO: Remove shared code +import * as dns from "dns"; +import { SocksClient } from "socks"; + +const DNS_LOOKUP_TIMEOUT_MS = 2000; +const SERVER_TEST_TIMEOUT_MS = 2000; +const CREDENTIALS_TEST_DOMAIN = "google.com"; +export const NS_PER_MILLI_SECOND = 1e6; + +export function timeoutPromise( + promise: Promise, + ms: number, + name = "" +): Promise { + let winner: Promise; + const timeout = new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + clearTimeout(timeoutId); + if (winner) { + console.log(`Promise "${name}" resolved before ${ms} ms.`); + resolve(); + } else { + console.log(`Promise "${name}" timed out after ${ms} ms.`); + reject("Promise timeout"); + } + }, ms); + }); + winner = Promise.race([promise, timeout]); + return winner; +} + +// Effectively a no-op if hostname is already an IP. +export function lookupIp(hostname: string) { + return timeoutPromise( + new Promise((fulfill, reject) => { + dns.lookup(hostname, 4, (e, address) => { + if (e || !address) { + return reject("could not resolve proxy server hostname"); + } + fulfill(address); + }); + }), + DNS_LOOKUP_TIMEOUT_MS, + "DNS lookup" + ); +} + +export function financial(x: number, fractionDigits = 2) { + return Number(Number.parseFloat(x.toString()).toFixed(fractionDigits)); +} + +// Resolves with true iff a response can be received from a semi-randomly-chosen website through the +// Shadowsocks proxy. +export const validateServerCredentials = (address: string, port: number) => + new Promise((fulfill, reject) => { + const lastTime = process.hrtime(); + SocksClient.createConnection({ + proxy: { host: address, port, type: 5 }, + destination: { host: CREDENTIALS_TEST_DOMAIN, port: 80 }, + command: "connect", + timeout: SERVER_TEST_TIMEOUT_MS + }) + .then(client => { + client.socket.write( + `HEAD / HTTP/1.1\r\nHost: ${CREDENTIALS_TEST_DOMAIN}\r\n\r\n` + ); + client.socket.on("data", data => { + if (data.toString().startsWith("HTTP/1.1")) { + client.socket.end(); + fulfill( + financial(process.hrtime(lastTime)[1] / NS_PER_MILLI_SECOND, 0) + ); + } else { + client.socket.end(); + reject("unexpected response from remote test website"); + } + }); + + client.socket.on("close", () => { + reject("could not connect to remote test website"); + }); + client.socket.resume(); + }) + .catch(e => { + reject(e); + }); + }); diff --git a/src/utils/connectivity.ts b/src/utils/connectivity.ts index 13fb13b..65587fb 100644 --- a/src/utils/connectivity.ts +++ b/src/utils/connectivity.ts @@ -13,12 +13,8 @@ // limitations under the License. import * as net from "net"; import * as dns from "dns"; -import { financial, timeoutPromise } from "./helper"; -import { SocksClient } from "socks"; +import { financial, NS_PER_MILLI_SECOND, timeoutPromise } from "../share"; -const SERVER_TEST_TIMEOUT_MS = 2000; -const CREDENTIALS_TEST_DOMAIN = "google.com"; -const NS_PER_MILLI_SECOND = 1e6; const DNS_TEST_SERVER = "8.8.8.8"; const DNS_TEST_DOMAIN = "google.com"; const DNS_TEST_TIMEOUT_MS = 2000; @@ -85,40 +81,3 @@ export const checkDns = () => }), DNS_TEST_TIMEOUT_MS ); - -// Resolves with true iff a response can be received from a semi-randomly-chosen website through the -// Shadowsocks proxy. -export const validateServerCredentials = (address: string, port: number) => - new Promise((fulfill, reject) => { - const lastTime = process.hrtime(); - SocksClient.createConnection({ - proxy: { host: address, port, type: 5 }, - destination: { host: CREDENTIALS_TEST_DOMAIN, port: 80 }, - command: "connect", - timeout: SERVER_TEST_TIMEOUT_MS - }) - .then(client => { - client.socket.write( - `HEAD / HTTP/1.1\r\nHost: ${CREDENTIALS_TEST_DOMAIN}\r\n\r\n` - ); - client.socket.on("data", data => { - if (data.toString().startsWith("HTTP/1.1")) { - client.socket.end(); - fulfill( - financial(process.hrtime(lastTime)[1] / NS_PER_MILLI_SECOND, 0) - ); - } else { - client.socket.end(); - reject("unexpected response from remote test website"); - } - }); - - client.socket.on("close", () => { - reject("could not connect to remote test website"); - }); - client.socket.resume(); - }) - .catch(e => { - reject(e); - }); - }); diff --git a/src/utils/helper.ts b/src/utils/helper.ts index c580331..31c1fe0 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -2,51 +2,12 @@ import regions from "i18n-iso-countries"; import path from "path"; import ip2region from "./ip2region"; import promiseIpc from "electron-promise-ipc"; -import * as dns from "dns"; - -const DNS_LOOKUP_TIMEOUT_MS = 2000; - -export function timeoutPromise( - promise: Promise, - ms: number, - name = "" -): Promise { - let winner: Promise; - const timeout = new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => { - clearTimeout(timeoutId); - if (winner) { - console.log(`Promise "${name}" resolved before ${ms} ms.`); - resolve(); - } else { - console.log(`Promise "${name}" timed out after ${ms} ms.`); - reject("Promise timeout"); - } - }, ms); - }); - winner = Promise.race([promise, timeout]); - return winner; -} +import { financial, lookupIp } from "../share"; regions.registerLocale(require("i18n-iso-countries/langs/zh.json")); // Uses the OS' built-in functions, i.e. /etc/hosts, et al.: // https://nodejs.org/dist/latest-v10.x/docs/api/dns.html#dns_dns -// Effectively a no-op if hostname is already an IP. -export function lookupIp(hostname: string) { - return timeoutPromise( - new Promise((fulfill, reject) => { - dns.lookup(hostname, 4, (e, address) => { - if (e || !address) { - return reject("could not resolve proxy server hostname"); - } - fulfill(address); - }); - }), - DNS_LOOKUP_TIMEOUT_MS, - "DNS lookup" - ); -} export const lookupRegionCodes = async (hosts: string[]) => { const ips = await Promise.all( @@ -94,10 +55,6 @@ export const lookupRegionCodes = async (hosts: string[]) => { return regionCodes; }; -export function financial(x: number, fractionDigits = 2) { - return Number(Number.parseFloat(x.toString()).toFixed(fractionDigits)); -} - export const convertTrafficData = (data: number) => { if (data < 1024) return `${financial(data)} B`; if (data < 1024 * 1024) return `${financial(data / 1024)} KB`;