feat: support setting proxy for app (#452)
* feat: implement proxy configuration management * feat: add proxy configuration setup during application boot * refactpr: update proxy configuration handling * feat: add custom hook for proxy configuration management * feat: add proxy configuration settings to general settings * fix: conditionally render NettingSetting based on electron env * fix: enhance proxy URL validation to check hostname and port * chore: clean code * chore: update Chinese localization * fix: update import path for tipcClient in useProxySetting hook
This commit is contained in:
parent
324eee23cb
commit
cfd527545a
|
|
@ -7,6 +7,7 @@ import squirrelStartup from "electron-squirrel-startup"
|
|||
|
||||
import { isDev, isMacOS } from "./env"
|
||||
import { initializeAppStage0, initializeAppStage1 } from "./init"
|
||||
import { updateProxy } from "./lib/proxy"
|
||||
import { setAuthSessionToken } from "./lib/user"
|
||||
import { registerUpdater } from "./updater"
|
||||
import { createMainWindow, createWindow } from "./window"
|
||||
|
|
@ -52,6 +53,7 @@ function bootstrap() {
|
|||
|
||||
mainWindow = createMainWindow()
|
||||
|
||||
updateProxy()
|
||||
registerUpdater()
|
||||
|
||||
//remove Electron, Follow from user agent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
import { session } from "electron"
|
||||
|
||||
import { logger } from "../logger"
|
||||
import { store } from "./store"
|
||||
|
||||
// Sets up the proxy configuration for the app.
|
||||
//
|
||||
// See https://www.electronjs.org/docs/latest/api/session#sessetproxyconfig
|
||||
// for more information about the proxy API.
|
||||
//
|
||||
// The open-source project [poooi/poi](https://github.com/poooi/poi) is doing well in proxy configuration
|
||||
// refer the following files for more details:
|
||||
//
|
||||
// https://github.com/poooi/poi/blob/5741d0d02c0a08626dd53196b094223457014491/lib/proxy.ts#L36
|
||||
// https://github.com/poooi/poi/blob/5741d0d02c0a08626dd53196b094223457014491/views/components/settings/network/index.es
|
||||
|
||||
export const setProxyConfig = (inputProxy: string) => {
|
||||
const proxyUri = normalizeProxyUri(inputProxy)
|
||||
if (!proxyUri) {
|
||||
return false
|
||||
}
|
||||
store.set("proxy", inputProxy)
|
||||
return true
|
||||
}
|
||||
|
||||
export const getProxyConfig = () => {
|
||||
const proxyConfig = store.get("proxy") as string | undefined
|
||||
if (!proxyConfig) {
|
||||
return
|
||||
}
|
||||
const proxyUri = normalizeProxyUri(proxyConfig)
|
||||
return proxyUri
|
||||
}
|
||||
|
||||
const URL_SCHEME = new Set(["http:", "https:", "ftp:", "socks:", "socks4:", "socks5:"])
|
||||
|
||||
const normalizeProxyUri = (userProxy: string) => {
|
||||
if (!userProxy) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const proxyUrl = new URL(userProxy)
|
||||
if (!URL_SCHEME.has(proxyUrl.protocol) || !proxyUrl.hostname || !proxyUrl.port) {
|
||||
return
|
||||
}
|
||||
// There are multiple ways to specify a proxy in Electron,
|
||||
// but for security reasons, we only support simple proxy URLs for now.
|
||||
return [
|
||||
`${proxyUrl.protocol}//${proxyUrl.hostname}:${proxyUrl.port}`,
|
||||
// Failing over to using no proxy if the proxy is unavailable
|
||||
"direct://",
|
||||
].join(",")
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const BYPASS_RULES = ["<local>"].join(";")
|
||||
|
||||
export const updateProxy = () => {
|
||||
const proxyUri = getProxyConfig()
|
||||
if (!proxyUri) {
|
||||
session.defaultSession.setProxy({
|
||||
// Note that the system mode is different from setting no proxy configuration.
|
||||
// In the latter case, Electron falls back to the system settings only if no command-line options influence the proxy configuration.
|
||||
mode: "system",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger.log(`Loading proxy: ${proxyUri}`)
|
||||
session.defaultSession.setProxy({
|
||||
proxyRules: proxyUri,
|
||||
proxyBypassRules: BYPASS_RULES,
|
||||
})
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import { createRequire } from "node:module"
|
|||
import { app, nativeTheme } from "electron"
|
||||
|
||||
import { setDockCount } from "../lib/dock"
|
||||
import { setProxyConfig, updateProxy } from "../lib/proxy"
|
||||
import { store } from "../lib/store"
|
||||
import { createSettingWindow } from "../window"
|
||||
import { t } from "./_instance"
|
||||
|
|
@ -39,4 +40,10 @@ export const settingRoute = {
|
|||
setDockBadge: t.procedure.input<number>().action(async ({ input }) => {
|
||||
setDockCount(input)
|
||||
}),
|
||||
getProxyConfig: t.procedure.action(async () => store.get("proxy")),
|
||||
setProxyConfig: t.procedure.input<string>().action(async ({ input }) => {
|
||||
const result = setProxyConfig(input)
|
||||
updateProxy()
|
||||
return result
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import { atom, useAtomValue, useSetAtom } from "jotai"
|
||||
import { useCallback } from "react"
|
||||
|
||||
import { tipcClient } from "~/lib/client"
|
||||
|
||||
const proxyAtom = atom("")
|
||||
|
||||
proxyAtom.onMount = (setAtom) => {
|
||||
tipcClient?.getProxyConfig().then((proxy: string) => {
|
||||
setAtom(proxy)
|
||||
})
|
||||
}
|
||||
|
||||
export const useProxyValue = () => useAtomValue(proxyAtom)
|
||||
|
||||
export const useSetProxy = () => {
|
||||
const setProxy = useSetAtom(proxyAtom)
|
||||
return useCallback(
|
||||
(proxyString: string) => {
|
||||
if (!window.electron) {
|
||||
return
|
||||
}
|
||||
setProxy(proxyString)
|
||||
tipcClient?.setProxyConfig(proxyString)
|
||||
},
|
||||
[setProxy],
|
||||
)
|
||||
}
|
||||
|
|
@ -22,12 +22,15 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "~/components/ui/select"
|
||||
import { useProxyValue, useSetProxy } from "~/hooks/common/useProxySetting"
|
||||
import { fallbackLanguage } from "~/i18n"
|
||||
import { initPostHog } from "~/initialize/posthog"
|
||||
import { tipcClient } from "~/lib/client"
|
||||
import { cn } from "~/lib/utils"
|
||||
import { clearLocalPersistStoreData } from "~/store/utils/clear"
|
||||
|
||||
import { SettingDescription, SettingInput } from "../control"
|
||||
import { SettingItemGroup } from "../section"
|
||||
import { SettingsTitle } from "../title"
|
||||
|
||||
const { defineSettingItem, SettingBuilder } = createSetting(
|
||||
|
|
@ -158,6 +161,9 @@ export const SettingGeneral = () => {
|
|||
description: t("general.rebuild_database.description"),
|
||||
buttonText: t("general.rebuild_database.button"),
|
||||
},
|
||||
|
||||
{ type: "title", value: t("general.network"), disabled: !window.electron },
|
||||
window.electron && NettingSetting,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -244,3 +250,22 @@ export const LanguageSelector = () => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const NettingSetting = () => {
|
||||
const { t } = useTranslation("settings")
|
||||
const proxyConfig = useProxyValue()
|
||||
const setProxyConfig = useSetProxy()
|
||||
|
||||
return (
|
||||
<SettingItemGroup>
|
||||
<SettingInput
|
||||
type="text"
|
||||
label={t("general.proxy")}
|
||||
labelClassName="w-[150px]"
|
||||
value={proxyConfig}
|
||||
onChange={(event) => setProxyConfig(event.target.value.trim())}
|
||||
/>
|
||||
<SettingDescription>{t("general.proxy.description")}</SettingDescription>
|
||||
</SettingItemGroup>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,10 @@
|
|||
"general.mark_as_read.render.label": "Mark as read when in the view",
|
||||
"general.mark_as_read.scroll.description": "Automatically mark entries as read when scrolled out of the view.",
|
||||
"general.mark_as_read.scroll.label": "Mark as read when scrolling",
|
||||
"general.network": "Network",
|
||||
"general.privacy_data": "Privacy & Data",
|
||||
"general.proxy": "Proxy",
|
||||
"general.proxy.description": "Set proxy for network traffic routing, e.g., socks://proxy.example.com:1080",
|
||||
"general.rebuild_database.button": "Rebuild",
|
||||
"general.rebuild_database.description": "If you are experiencing rendering issues, rebuilding the database may solve them.",
|
||||
"general.rebuild_database.label": "Rebuild Database",
|
||||
|
|
|
|||
|
|
@ -92,7 +92,10 @@
|
|||
"general.mark_as_read.render.label": "在窗口中时标记为已读",
|
||||
"general.mark_as_read.scroll.description": "当条目滚动出窗口时自动标记为已读",
|
||||
"general.mark_as_read.scroll.label": "滚动时标记为已读",
|
||||
"general.network": "网络",
|
||||
"general.privacy_data": "隐私与数据",
|
||||
"general.proxy": "代理",
|
||||
"general.proxy.description": "代理网络请求,示例:socks://proxy.example.com:1080",
|
||||
"general.rebuild_database.button": "重建",
|
||||
"general.rebuild_database.description": "尝试重建数据库可以解决部分渲染或其他类型问题",
|
||||
"general.rebuild_database.label": "重建数据库",
|
||||
|
|
|
|||
Loading…
Reference in New Issue