set proxy for Sub-Store and support control whether allow LAN connections to Sub-Store

This commit is contained in:
pompurin404 2024-10-15 13:50:19 +08:00
parent ea5397f250
commit 710780456a
No known key found for this signature in database
8 changed files with 249 additions and 149 deletions

View File

@ -1,8 +1,9 @@
### New Features ### New Features
- 支持多个订阅切换分别保存选择的节点 - Sub-Store 请求默认经过内核代理
- 支持在对话框直接拖动窗口 - 允许控制是否允许局域网连接 Sub-Store
- 全局模式时自动置顶 GLOBAL 代理组
### Bug Fixes ### Bug Fixes
- 修复订阅显示样式错误 - 修复下拉菜单组件样式异常

View File

@ -11,6 +11,8 @@ import express from 'express'
export let pacPort: number export let pacPort: number
export let subStorePort: number export let subStorePort: number
export let subStoreFrontendPort: number export let subStoreFrontendPort: number
let subStoreFrontendServer: http.Server
let subStoreBackendWorker: Worker
const defaultPacScript = ` const defaultPacScript = `
function FindProxyForURL(url, host) { function FindProxyForURL(url, host) {
@ -56,28 +58,42 @@ export async function startPacServer(): Promise<void> {
server.unref() server.unref()
} }
export async function startSubStoreServer(): Promise<void> { export async function startSubStoreFrontendServer(): Promise<void> {
const { useSubStore = true, subStoreHost = '127.0.0.1' } = await getAppConfig()
if (!useSubStore) return
await stopSubStoreFrontendServer()
subStoreFrontendPort = await findAvailablePort(14122)
const app = express()
app.use(express.static(path.join(resourcesFilesDir(), 'sub-store-frontend')))
subStoreFrontendServer = app.listen(subStoreFrontendPort, subStoreHost)
}
export async function stopSubStoreFrontendServer(): Promise<void> {
if (subStoreFrontendServer) {
subStoreFrontendServer.close()
}
}
export async function startSubStoreBackendServer(): Promise<void> {
const { const {
useSubStore = true, useSubStore = true,
useCustomSubStore = false, useCustomSubStore = false,
subStoreHost = '127.0.0.1',
subStoreBackendSyncCron = '', subStoreBackendSyncCron = '',
subStoreBackendDownloadCron = '', subStoreBackendDownloadCron = '',
subStoreBackendUploadCron = '' subStoreBackendUploadCron = ''
} = await getAppConfig() } = await getAppConfig()
const { 'mixed-port': port = 7890 } = await getControledMihomoConfig()
if (!useSubStore) return if (!useSubStore) return
if (!subStoreFrontendPort) { if (!useCustomSubStore) {
subStoreFrontendPort = await findAvailablePort(14122) await stopSubStoreBackendServer()
const app = express()
app.use(express.static(path.join(resourcesFilesDir(), 'sub-store-frontend')))
app.listen(subStoreFrontendPort)
}
if (!useCustomSubStore && !subStorePort) {
subStorePort = await findAvailablePort(38324) subStorePort = await findAvailablePort(38324)
const icon = nativeImage.createFromPath(subStoreIcon) const icon = nativeImage.createFromPath(subStoreIcon)
icon.toDataURL() icon.toDataURL()
new Worker(path.join(resourcesFilesDir(), 'sub-store.bundle.js'), { subStoreBackendWorker = new Worker(path.join(resourcesFilesDir(), 'sub-store.bundle.js'), {
env: { env: {
SUB_STORE_BACKEND_API_PORT: subStorePort.toString(), SUB_STORE_BACKEND_API_PORT: subStorePort.toString(),
SUB_STORE_BACKEND_API_HOST: subStoreHost,
SUB_STORE_DATA_BASE_PATH: subStoreDir(), SUB_STORE_DATA_BASE_PATH: subStoreDir(),
SUB_STORE_BACKEND_CUSTOM_ICON: icon.toDataURL(), SUB_STORE_BACKEND_CUSTOM_ICON: icon.toDataURL(),
SUB_STORE_BACKEND_CUSTOM_NAME: 'Mihomo Party', SUB_STORE_BACKEND_CUSTOM_NAME: 'Mihomo Party',
@ -85,8 +101,17 @@ export async function startSubStoreServer(): Promise<void> {
SUB_STORE_BACKEND_DOWNLOAD_CRON: subStoreBackendDownloadCron, SUB_STORE_BACKEND_DOWNLOAD_CRON: subStoreBackendDownloadCron,
SUB_STORE_BACKEND_UPLOAD_CRON: subStoreBackendUploadCron, SUB_STORE_BACKEND_UPLOAD_CRON: subStoreBackendUploadCron,
SUB_STORE_MMDB_COUNTRY_PATH: path.join(mihomoWorkDir(), 'country.mmdb'), SUB_STORE_MMDB_COUNTRY_PATH: path.join(mihomoWorkDir(), 'country.mmdb'),
SUB_STORE_MMDB_ASN_PATH: path.join(mihomoWorkDir(), 'ASN.mmdb') SUB_STORE_MMDB_ASN_PATH: path.join(mihomoWorkDir(), 'ASN.mmdb'),
HTTP_PROXY: `http://127.0.0.1:${port}`,
HTTPS_PROXY: `http://127.0.0.1:${port}`,
ALL_PROXY: `http://127.0.0.1:${port}`
} }
}) })
} }
} }
export async function stopSubStoreBackendServer(): Promise<void> {
if (subStoreBackendWorker) {
subStoreBackendWorker.terminate()
}
}

View File

@ -25,7 +25,11 @@ import yaml from 'yaml'
import { mkdir, writeFile, copyFile, rm, readdir } from 'fs/promises' import { mkdir, writeFile, copyFile, rm, readdir } from 'fs/promises'
import { existsSync } from 'fs' import { existsSync } from 'fs'
import path from 'path' import path from 'path'
import { startPacServer, startSubStoreServer } from '../resolve/server' import {
startPacServer,
startSubStoreBackendServer,
startSubStoreFrontendServer
} from '../resolve/server'
import { triggerSysProxy } from '../sys/sysproxy' import { triggerSysProxy } from '../sys/sysproxy'
import { import {
getAppConfig, getAppConfig,
@ -231,7 +235,8 @@ export async function init(): Promise<void> {
await initFiles() await initFiles()
await cleanup() await cleanup()
await startPacServer() await startPacServer()
await startSubStoreServer() await startSubStoreFrontendServer()
await startSubStoreBackendServer()
const { sysProxy } = await getAppConfig() const { sysProxy } = await getAppConfig()
try { try {
await triggerSysProxy(sysProxy.enable) await triggerSysProxy(sysProxy.enable)

View File

@ -43,7 +43,14 @@ import {
setOverride, setOverride,
updateOverrideItem updateOverrideItem
} from '../config' } from '../config'
import { startSubStoreServer, subStoreFrontendPort, subStorePort } from '../resolve/server' import {
startSubStoreFrontendServer,
startSubStoreBackendServer,
stopSubStoreFrontendServer,
stopSubStoreBackendServer,
subStoreFrontendPort,
subStorePort
} from '../resolve/server'
import { import {
isEncryptionAvailable, isEncryptionAvailable,
manualGrantCorePermition, manualGrantCorePermition,
@ -190,7 +197,13 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('registerShortcut', (_e, oldShortcut, newShortcut, action) => ipcMain.handle('registerShortcut', (_e, oldShortcut, newShortcut, action) =>
ipcErrorWrapper(registerShortcut)(oldShortcut, newShortcut, action) ipcErrorWrapper(registerShortcut)(oldShortcut, newShortcut, action)
) )
ipcMain.handle('startSubStoreServer', () => ipcErrorWrapper(startSubStoreServer)()) ipcMain.handle('startSubStoreFrontendServer', () =>
ipcErrorWrapper(startSubStoreFrontendServer)()
)
ipcMain.handle('stopSubStoreFrontendServer', () => ipcErrorWrapper(stopSubStoreFrontendServer)())
ipcMain.handle('startSubStoreBackendServer', () => ipcErrorWrapper(startSubStoreBackendServer)())
ipcMain.handle('stopSubStoreBackendServer', () => ipcErrorWrapper(stopSubStoreBackendServer)())
ipcMain.handle('subStorePort', () => subStorePort) ipcMain.handle('subStorePort', () => subStorePort)
ipcMain.handle('subStoreFrontendPort', () => subStoreFrontendPort) ipcMain.handle('subStoreFrontendPort', () => subStoreFrontendPort)
ipcMain.handle('subStoreSubs', () => ipcErrorWrapper(subStoreSubs)()) ipcMain.handle('subStoreSubs', () => ipcErrorWrapper(subStoreSubs)())

View File

@ -2,7 +2,12 @@ import React, { useState } from 'react'
import SettingCard from '@renderer/components/base/base-setting-card' import SettingCard from '@renderer/components/base/base-setting-card'
import SettingItem from '@renderer/components/base/base-setting-item' import SettingItem from '@renderer/components/base/base-setting-item'
import { Button, Input, Switch } from '@nextui-org/react' import { Button, Input, Switch } from '@nextui-org/react'
import { startSubStoreServer } from '@renderer/utils/ipc' import {
startSubStoreFrontendServer,
startSubStoreBackendServer,
stopSubStoreFrontendServer,
stopSubStoreBackendServer
} from '@renderer/utils/ipc'
import { useAppConfig } from '@renderer/hooks/use-app-config' import { useAppConfig } from '@renderer/hooks/use-app-config'
import debounce from '@renderer/utils/debounce' import debounce from '@renderer/utils/debounce'
import { isValidCron } from 'cron-validator' import { isValidCron } from 'cron-validator'
@ -12,6 +17,7 @@ const SubStoreConfig: React.FC = () => {
const { const {
useSubStore = true, useSubStore = true,
useCustomSubStore = false, useCustomSubStore = false,
subStoreHost = '127.0.0.1',
customSubStoreUrl, customSubStoreUrl,
subStoreBackendSyncCron, subStoreBackendSyncCron,
subStoreBackendDownloadCron, subStoreBackendDownloadCron,
@ -31,14 +37,20 @@ const SubStoreConfig: React.FC = () => {
useState(subStoreBackendUploadCron) useState(subStoreBackendUploadCron)
return ( return (
<SettingCard title="Sub-Store 设置"> <SettingCard title="Sub-Store 设置">
<SettingItem title="启用 Sub-Store" divider> <SettingItem title="启用 Sub-Store" divider={useSubStore}>
<Switch <Switch
size="sm" size="sm"
isSelected={useSubStore} isSelected={useSubStore}
onValueChange={async (v) => { onValueChange={async (v) => {
try { try {
await patchAppConfig({ useSubStore: v }) await patchAppConfig({ useSubStore: v })
if (v) await startSubStoreServer() if (v) {
await startSubStoreFrontendServer()
await startSubStoreBackendServer()
} else {
await stopSubStoreFrontendServer()
await stopSubStoreBackendServer()
}
} catch (e) { } catch (e) {
alert(e) alert(e)
} }
@ -46,138 +58,163 @@ const SubStoreConfig: React.FC = () => {
/> />
</SettingItem> </SettingItem>
{useSubStore && ( {useSubStore && (
<SettingItem title="使用自建 Sub-Store 后端" divider>
<Switch
size="sm"
isSelected={useCustomSubStore}
onValueChange={async (v) => {
try {
await patchAppConfig({ useCustomSubStore: v })
if (!v) await startSubStoreServer()
} catch (e) {
alert(e)
}
}}
/>
</SettingItem>
)}
{useCustomSubStore ? (
<SettingItem title="自建 Sub-Store 后端地址">
<Input
size="sm"
className="w-[60%]"
value={customSubStoreUrlValue}
placeholder="必须包含协议头"
onValueChange={(v: string) => {
setCustomSubStoreUrlValue(v)
setCustomSubStoreUrl(v)
}}
/>
</SettingItem>
) : (
<> <>
<SettingItem title="定时同步订阅/文件" divider> <SettingItem title="允许局域网连接" divider>
<div className="flex w-[60%] gap-2"> <Switch
{subStoreBackendSyncCronValue !== subStoreBackendSyncCron && ( size="sm"
<Button isSelected={subStoreHost === '0.0.0.0'}
size="sm" onValueChange={async (v) => {
color="primary" try {
onPress={async () => { if (v) {
if ( await patchAppConfig({ subStoreHost: '0.0.0.0' })
!subStoreBackendSyncCronValue || } else {
isValidCron(subStoreBackendSyncCronValue) await patchAppConfig({ subStoreHost: '127.0.0.1' })
) { }
await patchAppConfig({ await startSubStoreFrontendServer()
subStoreBackendSyncCron: subStoreBackendSyncCronValue await startSubStoreBackendServer()
}) } catch (e) {
new Notification('重启应用生效') alert(e)
} else { }
alert('Cron 表达式无效') }}
} />
}} </SettingItem>
> <SettingItem title="使用自建 Sub-Store 后端" divider>
<Switch
</Button> size="sm"
)} isSelected={useCustomSubStore}
onValueChange={async (v) => {
try {
await patchAppConfig({ useCustomSubStore: v })
if (v) {
await stopSubStoreBackendServer()
} else {
await startSubStoreBackendServer()
}
} catch (e) {
alert(e)
}
}}
/>
</SettingItem>
{useCustomSubStore ? (
<SettingItem title="自建 Sub-Store 后端地址">
<Input <Input
size="sm" size="sm"
className="flex-grown" className="w-[60%]"
value={subStoreBackendSyncCronValue} value={customSubStoreUrlValue}
placeholder="Cron 表达式" placeholder="必须包含协议头"
onValueChange={(v: string) => { onValueChange={(v: string) => {
setSubStoreBackendSyncCronValue(v) setCustomSubStoreUrlValue(v)
setCustomSubStoreUrl(v)
}} }}
/> />
</div> </SettingItem>
</SettingItem> ) : (
<SettingItem title="定时恢复配置" divider> <>
<div className="flex w-[60%] gap-2"> <SettingItem title="定时同步订阅/文件" divider>
{subStoreBackendDownloadCronValue !== subStoreBackendDownloadCron && ( <div className="flex w-[60%] gap-2">
<Button {subStoreBackendSyncCronValue !== subStoreBackendSyncCron && (
size="sm" <Button
color="primary" size="sm"
onPress={async () => { color="primary"
if ( onPress={async () => {
!subStoreBackendDownloadCronValue || if (
isValidCron(subStoreBackendDownloadCronValue) !subStoreBackendSyncCronValue ||
) { isValidCron(subStoreBackendSyncCronValue)
await patchAppConfig({ ) {
subStoreBackendDownloadCron: subStoreBackendDownloadCronValue await patchAppConfig({
}) subStoreBackendSyncCron: subStoreBackendSyncCronValue
new Notification('重启应用生效') })
} else { new Notification('重启应用生效')
alert('Cron 表达式无效') } else {
} alert('Cron 表达式无效')
}} }
> }}
>
</Button>
)} </Button>
<Input )}
size="sm" <Input
className="flex-grown" size="sm"
value={subStoreBackendDownloadCronValue} className="flex-grown"
placeholder="Cron 表达式" value={subStoreBackendSyncCronValue}
onValueChange={(v: string) => { placeholder="Cron 表达式"
setSubStoreBackendDownloadCronValue(v) onValueChange={(v: string) => {
}} setSubStoreBackendSyncCronValue(v)
/> }}
</div> />
</SettingItem> </div>
<SettingItem title="定时备份配置"> </SettingItem>
<div className="flex w-[60%] gap-2"> <SettingItem title="定时恢复配置" divider>
{subStoreBackendUploadCronValue !== subStoreBackendUploadCron && ( <div className="flex w-[60%] gap-2">
<Button {subStoreBackendDownloadCronValue !== subStoreBackendDownloadCron && (
size="sm" <Button
color="primary" size="sm"
onPress={async () => { color="primary"
if ( onPress={async () => {
!subStoreBackendUploadCronValue || if (
isValidCron(subStoreBackendUploadCronValue) !subStoreBackendDownloadCronValue ||
) { isValidCron(subStoreBackendDownloadCronValue)
await patchAppConfig({ ) {
subStoreBackendUploadCron: subStoreBackendUploadCronValue await patchAppConfig({
}) subStoreBackendDownloadCron: subStoreBackendDownloadCronValue
new Notification('重启应用生效') })
} else { new Notification('重启应用生效')
alert('Cron 表达式无效') } else {
} alert('Cron 表达式无效')
}} }
> }}
>
</Button>
)} </Button>
<Input )}
size="sm" <Input
className="flex-grown" size="sm"
value={subStoreBackendUploadCronValue} className="flex-grown"
placeholder="Cron 表达式" value={subStoreBackendDownloadCronValue}
onValueChange={(v: string) => { placeholder="Cron 表达式"
setSubStoreBackendUploadCronValue(v) onValueChange={(v: string) => {
}} setSubStoreBackendDownloadCronValue(v)
/> }}
</div> />
</SettingItem> </div>
</SettingItem>
<SettingItem title="定时备份配置">
<div className="flex w-[60%] gap-2">
{subStoreBackendUploadCronValue !== subStoreBackendUploadCron && (
<Button
size="sm"
color="primary"
onPress={async () => {
if (
!subStoreBackendUploadCronValue ||
isValidCron(subStoreBackendUploadCronValue)
) {
await patchAppConfig({
subStoreBackendUploadCron: subStoreBackendUploadCronValue
})
new Notification('重启应用生效')
} else {
alert('Cron 表达式无效')
}
}}
>
</Button>
)}
<Input
size="sm"
className="flex-grown"
value={subStoreBackendUploadCronValue}
placeholder="Cron 表达式"
onValueChange={(v: string) => {
setSubStoreBackendUploadCronValue(v)
}}
/>
</div>
</SettingItem>
</>
)}
</> </>
)} )}
</SettingCard> </SettingCard>

View File

@ -8,7 +8,12 @@ import { platform } from '@renderer/utils/init'
import { FaNetworkWired } from 'react-icons/fa' import { FaNetworkWired } from 'react-icons/fa'
import { IoMdCloudDownload } from 'react-icons/io' import { IoMdCloudDownload } from 'react-icons/io'
import PubSub from 'pubsub-js' import PubSub from 'pubsub-js'
import { mihomoUpgrade, restartCore, triggerSysProxy } from '@renderer/utils/ipc' import {
mihomoUpgrade,
restartCore,
startSubStoreBackendServer,
triggerSysProxy
} from '@renderer/utils/ipc'
import React, { useState } from 'react' import React, { useState } from 'react'
import InterfaceModal from '@renderer/components/mihomo/interface-modal' import InterfaceModal from '@renderer/components/mihomo/interface-modal'
import { MdDeleteForever } from 'react-icons/md' import { MdDeleteForever } from 'react-icons/md'
@ -129,6 +134,7 @@ const Mihomo: React.FC = () => {
className="mr-2" className="mr-2"
onPress={async () => { onPress={async () => {
await onChangeNeedRestart({ 'mixed-port': mixedPortInput }) await onChangeNeedRestart({ 'mixed-port': mixedPortInput })
await startSubStoreBackendServer()
if (sysProxy?.enable) { if (sysProxy?.enable) {
triggerSysProxy(true) triggerSysProxy(true)
} }

View File

@ -307,8 +307,20 @@ export async function getGistUrl(): Promise<string> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getGistUrl')) return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getGistUrl'))
} }
export async function startSubStoreServer(): Promise<void> { export async function startSubStoreFrontendServer(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('startSubStoreServer')) return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('startSubStoreFrontendServer'))
}
export async function stopSubStoreFrontendServer(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('stopSubStoreFrontendServer'))
}
export async function startSubStoreBackendServer(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('startSubStoreBackendServer'))
}
export async function stopSubStoreBackendServer(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('stopSubStoreBackendServer'))
} }
export async function subStorePort(): Promise<number> { export async function subStorePort(): Promise<number> {

View File

@ -231,6 +231,7 @@ interface IAppConfig {
tunCardStatus?: CardStatus tunCardStatus?: CardStatus
githubToken?: string githubToken?: string
useSubStore: boolean useSubStore: boolean
subStoreHost?: string
subStoreBackendSyncCron?: string subStoreBackendSyncCron?: string
subStoreBackendDownloadCron?: string subStoreBackendDownloadCron?: string
subStoreBackendUploadCron?: string subStoreBackendUploadCron?: string