mirror of
https://github.com/pompurin404/mihomo-party.git
synced 2024-11-16 11:42:19 +08:00
use async and optimize error handling
This commit is contained in:
parent
2b62a2f1e1
commit
2c5aa1a482
|
@ -1,22 +1,23 @@
|
|||
import { readFile, writeFile } from 'fs/promises'
|
||||
import { appConfigPath } from '../utils/dirs'
|
||||
import yaml from 'yaml'
|
||||
import fs from 'fs'
|
||||
|
||||
export let appConfig: IAppConfig // config.yaml
|
||||
let appConfig: IAppConfig // config.yaml
|
||||
|
||||
export function getAppConfig(force = false): IAppConfig {
|
||||
export async function getAppConfig(force = false): Promise<IAppConfig> {
|
||||
if (force || !appConfig) {
|
||||
appConfig = yaml.parse(fs.readFileSync(appConfigPath(), 'utf-8'))
|
||||
const data = await readFile(appConfigPath(), 'utf-8')
|
||||
appConfig = yaml.parse(data)
|
||||
}
|
||||
return appConfig
|
||||
}
|
||||
|
||||
export function setAppConfig(patch: Partial<IAppConfig>): void {
|
||||
export async function patchAppConfig(patch: Partial<IAppConfig>): Promise<void> {
|
||||
if (patch.sysProxy) {
|
||||
const oldSysProxy = appConfig.sysProxy || {}
|
||||
const newSysProxy = Object.assign(oldSysProxy, patch.sysProxy)
|
||||
patch.sysProxy = newSysProxy
|
||||
}
|
||||
appConfig = Object.assign(appConfig, patch)
|
||||
fs.writeFileSync(appConfigPath(), yaml.stringify(appConfig))
|
||||
await writeFile(appConfigPath(), yaml.stringify(appConfig))
|
||||
}
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import { controledMihomoConfigPath } from '../utils/dirs'
|
||||
import { readFile, writeFile } from 'fs/promises'
|
||||
import yaml from 'yaml'
|
||||
import fs from 'fs'
|
||||
import { getAxios, startMihomoMemory, startMihomoTraffic } from '../core/mihomoApi'
|
||||
import { generateProfile } from '../resolve/factory'
|
||||
import { getAppConfig } from './app'
|
||||
|
||||
export let controledMihomoConfig: Partial<IMihomoConfig> // mihomo.yaml
|
||||
let controledMihomoConfig: Partial<IMihomoConfig> // mihomo.yaml
|
||||
|
||||
export function getControledMihomoConfig(force = false): Partial<IMihomoConfig> {
|
||||
export async function getControledMihomoConfig(force = false): Promise<Partial<IMihomoConfig>> {
|
||||
if (force || !controledMihomoConfig) {
|
||||
controledMihomoConfig = yaml.parse(fs.readFileSync(controledMihomoConfigPath(), 'utf-8'))
|
||||
const data = await readFile(controledMihomoConfigPath(), 'utf-8')
|
||||
controledMihomoConfig = yaml.parse(data)
|
||||
}
|
||||
return controledMihomoConfig
|
||||
}
|
||||
|
||||
export function setControledMihomoConfig(patch: Partial<IMihomoConfig>): void {
|
||||
const { useNameserverPolicy } = getAppConfig()
|
||||
export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
|
||||
const { useNameserverPolicy } = await getAppConfig()
|
||||
if (patch.tun) {
|
||||
const oldTun = controledMihomoConfig.tun || {}
|
||||
const newTun = Object.assign(oldTun, patch.tun)
|
||||
|
@ -35,11 +36,12 @@ export function setControledMihomoConfig(patch: Partial<IMihomoConfig>): void {
|
|||
patch.sniffer = newSniffer
|
||||
}
|
||||
controledMihomoConfig = Object.assign(controledMihomoConfig, patch)
|
||||
|
||||
if (patch['external-controller'] || patch.secret) {
|
||||
getAxios(true)
|
||||
startMihomoMemory()
|
||||
startMihomoTraffic()
|
||||
await getAxios(true)
|
||||
await startMihomoMemory()
|
||||
await startMihomoTraffic()
|
||||
}
|
||||
generateProfile()
|
||||
fs.writeFileSync(controledMihomoConfigPath(), yaml.stringify(controledMihomoConfig))
|
||||
await generateProfile()
|
||||
await writeFile(controledMihomoConfigPath(), yaml.stringify(controledMihomoConfig), 'utf-8')
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export { getAppConfig, setAppConfig } from './app'
|
||||
export { getControledMihomoConfig, setControledMihomoConfig } from './controledMihomo'
|
||||
export { getAppConfig, patchAppConfig } from './app'
|
||||
export { getControledMihomoConfig, patchControledMihomoConfig } from './controledMihomo'
|
||||
export {
|
||||
getProfile,
|
||||
getCurrentProfileItem,
|
||||
|
|
|
@ -1,47 +1,56 @@
|
|||
import { overrideConfigPath, overridePath } from '../utils/dirs'
|
||||
import yaml from 'yaml'
|
||||
import fs from 'fs'
|
||||
import { dialog } from 'electron'
|
||||
import axios from 'axios'
|
||||
import { getControledMihomoConfig } from './controledMihomo'
|
||||
import { readFile, writeFile, rm } from 'fs/promises'
|
||||
import { existsSync } from 'fs'
|
||||
import axios from 'axios'
|
||||
import yaml from 'yaml'
|
||||
|
||||
let overrideConfig: IOverrideConfig // override.yaml
|
||||
|
||||
export function getOverrideConfig(force = false): IOverrideConfig {
|
||||
export async function getOverrideConfig(force = false): Promise<IOverrideConfig> {
|
||||
if (force || !overrideConfig) {
|
||||
overrideConfig = yaml.parse(fs.readFileSync(overrideConfigPath(), 'utf-8'))
|
||||
const data = await readFile(overrideConfigPath(), 'utf-8')
|
||||
overrideConfig = yaml.parse(data)
|
||||
}
|
||||
return overrideConfig
|
||||
}
|
||||
|
||||
export function setOverrideConfig(config: IOverrideConfig): void {
|
||||
export async function setOverrideConfig(config: IOverrideConfig): Promise<void> {
|
||||
overrideConfig = config
|
||||
fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig))
|
||||
await writeFile(overrideConfigPath(), yaml.stringify(overrideConfig), 'utf-8')
|
||||
}
|
||||
|
||||
export function getOverrideItem(id: string): IOverrideItem | undefined {
|
||||
return overrideConfig.items.find((item) => item.id === id)
|
||||
export async function getOverrideItem(id: string | undefined): Promise<IOverrideItem | undefined> {
|
||||
const { items } = await getOverrideConfig()
|
||||
return items.find((item) => item.id === id)
|
||||
}
|
||||
export function updateOverrideItem(item: IOverrideItem): void {
|
||||
const index = overrideConfig.items.findIndex((i) => i.id === item.id)
|
||||
overrideConfig.items[index] = item
|
||||
fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig))
|
||||
|
||||
export async function updateOverrideItem(item: IOverrideItem): Promise<void> {
|
||||
const config = await getOverrideConfig()
|
||||
const index = config.items.findIndex((i) => i.id === item.id)
|
||||
if (index === -1) {
|
||||
throw new Error('Override not found')
|
||||
}
|
||||
config.items[index] = item
|
||||
await setOverrideConfig(config)
|
||||
}
|
||||
|
||||
export async function addOverrideItem(item: Partial<IOverrideItem>): Promise<void> {
|
||||
const config = await getOverrideConfig()
|
||||
const newItem = await createOverride(item)
|
||||
if (overrideConfig.items.find((i) => i.id === newItem.id)) {
|
||||
if (await getOverrideItem(item.id)) {
|
||||
updateOverrideItem(newItem)
|
||||
} else {
|
||||
overrideConfig.items.push(newItem)
|
||||
config.items.push(newItem)
|
||||
}
|
||||
fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig))
|
||||
await setOverrideConfig(config)
|
||||
}
|
||||
|
||||
export function removeOverrideItem(id: string): void {
|
||||
overrideConfig.items = overrideConfig.items?.filter((item) => item.id !== id)
|
||||
fs.writeFileSync(overrideConfigPath(), yaml.stringify(overrideConfig))
|
||||
fs.rmSync(overridePath(id))
|
||||
export async function removeOverrideItem(id: string): Promise<void> {
|
||||
const config = await getOverrideConfig()
|
||||
config.items = config.items?.filter((item) => item.id !== id)
|
||||
await setOverrideConfig(config)
|
||||
await rm(overridePath(id))
|
||||
}
|
||||
|
||||
export async function createOverride(item: Partial<IOverrideItem>): Promise<IOverrideItem> {
|
||||
|
@ -55,31 +64,21 @@ export async function createOverride(item: Partial<IOverrideItem>): Promise<IOve
|
|||
} as IOverrideItem
|
||||
switch (newItem.type) {
|
||||
case 'remote': {
|
||||
if (!item.url) {
|
||||
throw new Error('URL is required for remote script')
|
||||
}
|
||||
try {
|
||||
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
||||
if (!item.url) throw new Error('Empty URL')
|
||||
const res = await axios.get(item.url, {
|
||||
proxy: {
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: getControledMihomoConfig()['mixed-port'] || 7890
|
||||
},
|
||||
responseType: 'text'
|
||||
port: mixedPort
|
||||
}
|
||||
})
|
||||
const data = res.data
|
||||
setOverride(id, data)
|
||||
} catch (e) {
|
||||
dialog.showErrorBox('Failed to fetch remote script', `${e}\nurl: ${item.url}`)
|
||||
throw new Error(`Failed to fetch remote script ${e}`)
|
||||
}
|
||||
await setOverride(id, data)
|
||||
break
|
||||
}
|
||||
case 'local': {
|
||||
if (!item.file) {
|
||||
throw new Error('File is required for local script')
|
||||
}
|
||||
const data = item.file
|
||||
const data = item.file || ''
|
||||
setOverride(id, data)
|
||||
break
|
||||
}
|
||||
|
@ -88,13 +87,13 @@ export async function createOverride(item: Partial<IOverrideItem>): Promise<IOve
|
|||
return newItem
|
||||
}
|
||||
|
||||
export function getOverride(id: string): string {
|
||||
if (!fs.existsSync(overridePath(id))) {
|
||||
export async function getOverride(id: string): Promise<string> {
|
||||
if (!existsSync(overridePath(id))) {
|
||||
return `function main(config){ return config }`
|
||||
}
|
||||
return fs.readFileSync(overridePath(id), 'utf-8')
|
||||
return await readFile(overridePath(id), 'utf-8')
|
||||
}
|
||||
|
||||
export function setOverride(id: string, content: string): void {
|
||||
fs.writeFileSync(overridePath(id), content, 'utf-8')
|
||||
export async function setOverride(id: string, content: string): Promise<void> {
|
||||
await writeFile(overridePath(id), content, 'utf-8')
|
||||
}
|
||||
|
|
|
@ -1,87 +1,168 @@
|
|||
import { getControledMihomoConfig } from './controledMihomo'
|
||||
import { profileConfigPath, profilePath } from '../utils/dirs'
|
||||
import { addProfileUpdater } from '../core/profileUpdater'
|
||||
import { readFile, rm, writeFile } from 'fs/promises'
|
||||
import { restartCore } from '../core/manager'
|
||||
import { getAppConfig } from './app'
|
||||
import { window } from '..'
|
||||
import { mainWindow } from '..'
|
||||
import { existsSync } from 'fs'
|
||||
import axios from 'axios'
|
||||
import yaml from 'yaml'
|
||||
import fs from 'fs'
|
||||
import { dialog } from 'electron'
|
||||
import { addProfileUpdater } from '../core/profileUpdater'
|
||||
import { defaultProfile } from '../utils/template'
|
||||
|
||||
let profileConfig: IProfileConfig // profile.yaml
|
||||
|
||||
export function getProfileConfig(force = false): IProfileConfig {
|
||||
export async function getProfileConfig(force = false): Promise<IProfileConfig> {
|
||||
if (force || !profileConfig) {
|
||||
profileConfig = yaml.parse(fs.readFileSync(profileConfigPath(), 'utf-8'))
|
||||
const data = await readFile(profileConfigPath(), 'utf-8')
|
||||
profileConfig = yaml.parse(data)
|
||||
}
|
||||
return profileConfig
|
||||
}
|
||||
|
||||
export function setProfileConfig(config: IProfileConfig): void {
|
||||
export async function setProfileConfig(config: IProfileConfig): Promise<void> {
|
||||
profileConfig = config
|
||||
window?.webContents.send('profileConfigUpdated')
|
||||
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
||||
mainWindow?.webContents.send('profileConfigUpdated')
|
||||
await writeFile(profileConfigPath(), yaml.stringify(config), 'utf-8')
|
||||
}
|
||||
|
||||
export function getProfileItem(id: string | undefined): IProfileItem {
|
||||
const items = getProfileConfig().items
|
||||
return items?.find((item) => item.id === id) || { id: 'default', type: 'local', name: '空白订阅' }
|
||||
export async function getProfileItem(id: string | undefined): Promise<IProfileItem | undefined> {
|
||||
const { items } = await getProfileConfig()
|
||||
if (!id || id === 'default') return { id: 'default', type: 'local', name: '空白订阅' }
|
||||
return items.find((item) => item.id === id)
|
||||
}
|
||||
|
||||
export async function changeCurrentProfile(id: string): Promise<void> {
|
||||
const oldId = getProfileConfig().current
|
||||
profileConfig.current = id
|
||||
const config = await getProfileConfig()
|
||||
const current = config.current
|
||||
config.current = id
|
||||
await setProfileConfig(config)
|
||||
try {
|
||||
await restartCore()
|
||||
} catch (e) {
|
||||
profileConfig.current = oldId
|
||||
config.current = current
|
||||
throw e
|
||||
} finally {
|
||||
window?.webContents.send('profileConfigUpdated')
|
||||
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
||||
await setProfileConfig(config)
|
||||
}
|
||||
}
|
||||
|
||||
export function updateProfileItem(item: IProfileItem): void {
|
||||
const index = profileConfig.items.findIndex((i) => i.id === item.id)
|
||||
profileConfig.items[index] = item
|
||||
addProfileUpdater(item.id)
|
||||
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
||||
window?.webContents.send('profileConfigUpdated')
|
||||
export async function updateProfileItem(item: IProfileItem): Promise<void> {
|
||||
const config = await getProfileConfig()
|
||||
const index = config.items.findIndex((i) => i.id === item.id)
|
||||
if (index === -1) {
|
||||
throw new Error('Profile not found')
|
||||
}
|
||||
config.items[index] = item
|
||||
await setProfileConfig(config)
|
||||
await addProfileUpdater(item)
|
||||
}
|
||||
|
||||
export async function addProfileItem(item: Partial<IProfileItem>): Promise<void> {
|
||||
const newItem = await createProfile(item)
|
||||
if (profileConfig.items.find((i) => i.id === newItem.id)) {
|
||||
updateProfileItem(newItem)
|
||||
const config = await getProfileConfig()
|
||||
if (await getProfileItem(item.id)) {
|
||||
await updateProfileItem(newItem)
|
||||
} else {
|
||||
profileConfig.items.push(newItem)
|
||||
config.items.push(newItem)
|
||||
}
|
||||
await setProfileConfig(config)
|
||||
|
||||
if (!config.current) {
|
||||
await changeCurrentProfile(newItem.id)
|
||||
}
|
||||
await addProfileUpdater(newItem)
|
||||
}
|
||||
|
||||
if (!getProfileConfig().current) {
|
||||
changeCurrentProfile(newItem.id)
|
||||
}
|
||||
addProfileUpdater(newItem.id)
|
||||
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
||||
window?.webContents.send('profileConfigUpdated')
|
||||
}
|
||||
|
||||
export function removeProfileItem(id: string): void {
|
||||
profileConfig.items = profileConfig.items?.filter((item) => item.id !== id)
|
||||
if (profileConfig.current === id) {
|
||||
if (profileConfig.items.length > 0) {
|
||||
profileConfig.current = profileConfig.items[0]?.id
|
||||
export async function removeProfileItem(id: string): Promise<void> {
|
||||
const config = await getProfileConfig()
|
||||
config.items = config.items?.filter((item) => item.id !== id)
|
||||
if (config.current === id) {
|
||||
if (config.items.length > 0) {
|
||||
config.current = config.items[0].id
|
||||
} else {
|
||||
profileConfig.current = undefined
|
||||
config.current = undefined
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(profileConfigPath(), yaml.stringify(profileConfig))
|
||||
fs.rmSync(profilePath(id))
|
||||
window?.webContents.send('profileConfigUpdated')
|
||||
await setProfileConfig(config)
|
||||
if (existsSync(profilePath(id))) {
|
||||
await rm(profilePath(id))
|
||||
}
|
||||
}
|
||||
|
||||
export function getCurrentProfileItem(): IProfileItem {
|
||||
return getProfileItem(getProfileConfig().current)
|
||||
export async function getCurrentProfileItem(): Promise<IProfileItem> {
|
||||
const { current } = await getProfileConfig()
|
||||
return (await getProfileItem(current)) || { id: 'default', type: 'local', name: '空白订阅' }
|
||||
}
|
||||
|
||||
export async function createProfile(item: Partial<IProfileItem>): Promise<IProfileItem> {
|
||||
const id = item.id || new Date().getTime().toString(16)
|
||||
const newItem = {
|
||||
id,
|
||||
name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'),
|
||||
type: item.type,
|
||||
url: item.url,
|
||||
interval: item.interval || 0,
|
||||
updated: new Date().getTime()
|
||||
} as IProfileItem
|
||||
switch (newItem.type) {
|
||||
case 'remote': {
|
||||
const { userAgent = 'clash-meta' } = await getAppConfig()
|
||||
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
||||
if (!item.url) throw new Error('Empty URL')
|
||||
const res = await axios.get(item.url, {
|
||||
proxy: {
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: mixedPort
|
||||
},
|
||||
headers: {
|
||||
'User-Agent': userAgent
|
||||
}
|
||||
})
|
||||
const data = res.data
|
||||
const headers = res.headers
|
||||
if (headers['content-disposition'] && newItem.name === 'Remote File') {
|
||||
newItem.name = parseFilename(headers['content-disposition'])
|
||||
}
|
||||
if (headers['profile-web-page-url']) {
|
||||
newItem.home = headers['profile-web-page-url']
|
||||
}
|
||||
if (headers['profile-update-interval']) {
|
||||
newItem.interval = parseInt(headers['profile-update-interval']) * 60
|
||||
}
|
||||
if (headers['subscription-userinfo']) {
|
||||
newItem.extra = parseSubinfo(headers['subscription-userinfo'])
|
||||
}
|
||||
await setProfileStr(id, data)
|
||||
break
|
||||
}
|
||||
case 'local': {
|
||||
const data = item.file || ''
|
||||
await setProfileStr(id, data)
|
||||
break
|
||||
}
|
||||
}
|
||||
return newItem
|
||||
}
|
||||
|
||||
export async function getProfileStr(id: string | undefined): Promise<string> {
|
||||
if (existsSync(profilePath(id || 'default'))) {
|
||||
return await readFile(profilePath(id || 'default'), 'utf-8')
|
||||
} else {
|
||||
return yaml.stringify(defaultProfile)
|
||||
}
|
||||
}
|
||||
|
||||
export async function setProfileStr(id: string, content: string): Promise<void> {
|
||||
const { current } = await getProfileConfig()
|
||||
await writeFile(profilePath(id), content, 'utf-8')
|
||||
if (current === id) await restartCore()
|
||||
}
|
||||
|
||||
export async function getProfile(id: string | undefined): Promise<IMihomoConfig> {
|
||||
const profile = await getProfileStr(id)
|
||||
return yaml.parse(profile)
|
||||
}
|
||||
|
||||
// attachment;filename=xxx.yaml; filename*=UTF-8''%xx%xx%xx
|
||||
|
@ -105,80 +186,3 @@ function parseSubinfo(str: string): ISubscriptionUserInfo {
|
|||
})
|
||||
return obj
|
||||
}
|
||||
|
||||
export async function createProfile(item: Partial<IProfileItem>): Promise<IProfileItem> {
|
||||
const id = item.id || new Date().getTime().toString(16)
|
||||
const newItem = {
|
||||
id,
|
||||
name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'),
|
||||
type: item.type,
|
||||
url: item.url,
|
||||
interval: item.interval || 0,
|
||||
updated: new Date().getTime()
|
||||
} as IProfileItem
|
||||
switch (newItem.type) {
|
||||
case 'remote': {
|
||||
if (!item.url) {
|
||||
throw new Error('URL is required for remote profile')
|
||||
}
|
||||
try {
|
||||
const ua = getAppConfig().userAgent || 'clash-meta'
|
||||
const res = await axios.get(item.url, {
|
||||
proxy: {
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: getControledMihomoConfig()['mixed-port'] || 7890
|
||||
},
|
||||
headers: {
|
||||
'User-Agent': ua
|
||||
},
|
||||
responseType: 'text'
|
||||
})
|
||||
const data = res.data
|
||||
const headers = res.headers
|
||||
if (headers['content-disposition'] && newItem.name === 'Remote File') {
|
||||
newItem.name = parseFilename(headers['content-disposition'])
|
||||
}
|
||||
if (headers['profile-web-page-url']) {
|
||||
newItem.home = headers['profile-web-page-url']
|
||||
}
|
||||
if (headers['profile-update-interval']) {
|
||||
newItem.interval = parseInt(headers['profile-update-interval']) * 60
|
||||
}
|
||||
if (headers['subscription-userinfo']) {
|
||||
newItem.extra = parseSubinfo(headers['subscription-userinfo'])
|
||||
}
|
||||
await setProfileStr(id, data)
|
||||
} catch (e) {
|
||||
dialog.showErrorBox('Failed to fetch remote profile', `${e}\nurl: ${item.url}`)
|
||||
throw new Error(`Failed to fetch remote profile ${e}`)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'local': {
|
||||
if (!item.file) {
|
||||
throw new Error('File is required for local profile')
|
||||
}
|
||||
const data = item.file
|
||||
await setProfileStr(id, data)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return newItem
|
||||
}
|
||||
|
||||
export function getProfileStr(id: string): string {
|
||||
return fs.readFileSync(profilePath(id), 'utf-8')
|
||||
}
|
||||
|
||||
export async function setProfileStr(id: string, content: string): Promise<void> {
|
||||
fs.writeFileSync(profilePath(id), content, 'utf-8')
|
||||
if (id === getProfileConfig().current) {
|
||||
await restartCore()
|
||||
}
|
||||
}
|
||||
|
||||
export function getProfile(id: string | undefined): IMihomoConfig {
|
||||
return yaml.parse(getProfileStr(id || 'default'))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ChildProcess, execFile, execSync, spawn } from 'child_process'
|
||||
import { ChildProcess, exec, execFile, spawn } from 'child_process'
|
||||
import {
|
||||
logPath,
|
||||
mihomoCorePath,
|
||||
|
@ -7,29 +7,44 @@ import {
|
|||
mihomoWorkDir
|
||||
} from '../utils/dirs'
|
||||
import { generateProfile } from '../resolve/factory'
|
||||
import { getAppConfig, setAppConfig } from '../config'
|
||||
import { getAppConfig, patchAppConfig } from '../config'
|
||||
import { dialog, safeStorage } from 'electron'
|
||||
import fs from 'fs'
|
||||
import { pauseWebsockets } from './mihomoApi'
|
||||
import { writeFile } from 'fs/promises'
|
||||
import { promisify } from 'util'
|
||||
|
||||
let child: ChildProcess
|
||||
let retry = 10
|
||||
|
||||
export async function startCore(): Promise<void> {
|
||||
const corePath = mihomoCorePath(getAppConfig().core ?? 'mihomo')
|
||||
grantCorePermition(corePath)
|
||||
generateProfile()
|
||||
const { core = 'mihomo' } = await getAppConfig()
|
||||
const corePath = mihomoCorePath(core)
|
||||
await grantCorePermition(corePath)
|
||||
await generateProfile()
|
||||
await checkProfile()
|
||||
stopCore()
|
||||
return new Promise((resolve, reject) => {
|
||||
child = spawn(corePath, ['-d', mihomoWorkDir()])
|
||||
child.stdout?.on('data', (data) => {
|
||||
child.on('close', async (code, signal) => {
|
||||
await writeFile(logPath(), `[Manager]: Core closed, code: ${code}, signal: ${signal}\n`, {
|
||||
flag: 'a'
|
||||
})
|
||||
if (retry) {
|
||||
await writeFile(logPath(), `[Manager]: Try Restart Core\n`, { flag: 'a' })
|
||||
retry--
|
||||
await restartCore()
|
||||
} else {
|
||||
stopCore()
|
||||
}
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
child.stdout?.on('data', async (data) => {
|
||||
if (data.toString().includes('External controller listen error')) {
|
||||
if (retry) {
|
||||
retry--
|
||||
resolve(startCore())
|
||||
resolve(await startCore())
|
||||
} else {
|
||||
dialog.showErrorBox('External controller listen error', data.toString())
|
||||
dialog.showErrorBox('内核连接失败', '请尝试更改外部控制端口后重启内核')
|
||||
stopCore()
|
||||
reject('External controller listen error')
|
||||
}
|
||||
}
|
||||
|
@ -37,36 +52,7 @@ export async function startCore(): Promise<void> {
|
|||
retry = 10
|
||||
resolve()
|
||||
}
|
||||
fs.writeFileSync(
|
||||
logPath(),
|
||||
data
|
||||
.toString()
|
||||
.split('\n')
|
||||
.map((line: string) => {
|
||||
if (line) return `[Mihomo]: ${line}`
|
||||
return ''
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
{
|
||||
flag: 'a'
|
||||
}
|
||||
)
|
||||
})
|
||||
child.on('close', async (code, signal) => {
|
||||
fs.writeFileSync(logPath(), `[Manager]: Core closed, code: ${code}, signal: ${signal}\n`, {
|
||||
flag: 'a'
|
||||
})
|
||||
fs.writeFileSync(logPath(), `[Manager]: Restart Core\n`, {
|
||||
flag: 'a'
|
||||
})
|
||||
if (retry) {
|
||||
retry--
|
||||
await restartCore()
|
||||
} else {
|
||||
dialog.showErrorBox('Mihomo Core Closed', `Core closed, code: ${code}, signal: ${signal}`)
|
||||
stopCore()
|
||||
}
|
||||
await writeFile(logPath(), data, { flag: 'a' })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -84,44 +70,49 @@ export async function restartCore(): Promise<void> {
|
|||
recover()
|
||||
}
|
||||
|
||||
export function checkProfile(): Promise<void> {
|
||||
const corePath = mihomoCorePath(getAppConfig().core ?? 'mihomo')
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = execFile(corePath, ['-t', '-f', mihomoWorkConfigPath(), '-d', mihomoTestDir()])
|
||||
child.stdout?.on('data', (data) => {
|
||||
data
|
||||
.toString()
|
||||
async function checkProfile(): Promise<void> {
|
||||
const { core = 'mihomo' } = await getAppConfig()
|
||||
const corePath = mihomoCorePath(core)
|
||||
const execFilePromise = promisify(execFile)
|
||||
try {
|
||||
await execFilePromise(corePath, ['-t', '-f', mihomoWorkConfigPath(), '-d', mihomoTestDir()])
|
||||
} catch (error) {
|
||||
if (error instanceof Error && 'stdout' in error) {
|
||||
const { stdout } = error as { stdout: string }
|
||||
const errorLines = stdout
|
||||
.split('\n')
|
||||
.forEach((line: string) => {
|
||||
if (line.includes('level=error')) {
|
||||
dialog.showErrorBox('Profile Check Failed', line.split('level=error')[1])
|
||||
reject(line)
|
||||
.filter((line) => line.includes('level=error'))
|
||||
.map((line) => line.split('level=error')[1])
|
||||
throw new Error(`Profile Check Failed:\n${errorLines.join('\n')}`)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
})
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function grantCorePermition(corePath: string): void {
|
||||
if (getAppConfig().encryptedPassword && isEncryptionAvailable()) {
|
||||
const password = safeStorage.decryptString(Buffer.from(getAppConfig().encryptedPassword ?? []))
|
||||
try {
|
||||
export async function grantCorePermition(corePath: string): Promise<void> {
|
||||
const { encryptedPassword } = await getAppConfig()
|
||||
const execPromise = promisify(exec)
|
||||
if (encryptedPassword && isEncryptionAvailable()) {
|
||||
const password = safeStorage.decryptString(Buffer.from(encryptedPassword))
|
||||
if (process.platform === 'linux') {
|
||||
execSync(
|
||||
try {
|
||||
await execPromise(
|
||||
`echo "${password}" | sudo -S setcap cap_net_bind_service,cap_net_admin,cap_sys_ptrace,cap_dac_read_search,cap_dac_override,cap_net_raw=+ep ${corePath}`
|
||||
)
|
||||
} catch (error) {
|
||||
patchAppConfig({ encryptedPassword: undefined })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
if (process.platform === 'darwin') {
|
||||
execSync(`echo "${password}" | sudo -S chown root:admin ${corePath}`)
|
||||
execSync(`echo "${password}" | sudo -S chmod +sx ${corePath}`)
|
||||
try {
|
||||
await execPromise(`echo "${password}" | sudo -S chown root:admin ${corePath}`)
|
||||
await execPromise(`echo "${password}" | sudo -S chmod +sx ${corePath}`)
|
||||
} catch (error) {
|
||||
patchAppConfig({ encryptedPassword: undefined })
|
||||
throw error
|
||||
}
|
||||
} catch (e) {
|
||||
setAppConfig({ encryptedPassword: undefined })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import axios, { AxiosInstance } from 'axios'
|
||||
import { getAppConfig, getControledMihomoConfig } from '../config'
|
||||
import { mainWindow } from '..'
|
||||
import WebSocket from 'ws'
|
||||
import { window } from '..'
|
||||
|
||||
let axiosIns: AxiosInstance = null!
|
||||
let mihomoTrafficWs: WebSocket | null = null
|
||||
|
@ -15,9 +15,9 @@ let connectionsRetry = 10
|
|||
|
||||
export const getAxios = async (force: boolean = false): Promise<AxiosInstance> => {
|
||||
if (axiosIns && !force) return axiosIns
|
||||
|
||||
let server = getControledMihomoConfig()['external-controller']
|
||||
const secret = getControledMihomoConfig().secret ?? ''
|
||||
const controledMihomoConfig = await getControledMihomoConfig()
|
||||
let server = controledMihomoConfig['external-controller']
|
||||
const secret = controledMihomoConfig.secret ?? ''
|
||||
if (server?.startsWith(':')) server = `127.0.0.1${server}`
|
||||
|
||||
axiosIns = axios.create({
|
||||
|
@ -26,138 +26,127 @@ export const getAxios = async (force: boolean = false): Promise<AxiosInstance> =
|
|||
headers: secret ? { Authorization: `Bearer ${secret}` } : {},
|
||||
timeout: 15000
|
||||
})
|
||||
axiosIns.interceptors.response.use((r) => r.data)
|
||||
|
||||
axiosIns.interceptors.response.use(
|
||||
(response) => {
|
||||
return response.data
|
||||
},
|
||||
(error) => {
|
||||
if (error.response && error.response.data) {
|
||||
return Promise.reject(error.response.data)
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
return axiosIns
|
||||
}
|
||||
|
||||
export async function mihomoVersion(): Promise<IMihomoVersion> {
|
||||
const instance = await getAxios()
|
||||
return (await instance.get('/version').catch(() => {
|
||||
return { version: '-' }
|
||||
})) as IMihomoVersion
|
||||
try {
|
||||
return await instance.get('/version')
|
||||
} catch (error) {
|
||||
return { version: '-', meta: true }
|
||||
}
|
||||
}
|
||||
|
||||
export const patchMihomoConfig = async (patch: Partial<IMihomoConfig>): Promise<void> => {
|
||||
const instance = await getAxios()
|
||||
return (await instance.patch('/configs', patch).catch((e) => {
|
||||
return e.response.data
|
||||
})) as Promise<void>
|
||||
return await instance.patch('/configs', patch)
|
||||
}
|
||||
|
||||
export const mihomoCloseConnection = async (id: string): Promise<void> => {
|
||||
const instance = await getAxios()
|
||||
return (await instance.delete(`/connections/${encodeURIComponent(id)}`).catch((e) => {
|
||||
return e.response.data
|
||||
})) as Promise<void>
|
||||
return await instance.delete(`/connections/${encodeURIComponent(id)}`)
|
||||
}
|
||||
|
||||
export const mihomoCloseAllConnections = async (): Promise<void> => {
|
||||
const instance = await getAxios()
|
||||
return (await instance.delete('/connections').catch((e) => {
|
||||
return e.response.data
|
||||
})) as Promise<void>
|
||||
return await instance.delete('/connections')
|
||||
}
|
||||
|
||||
export const mihomoRules = async (): Promise<IMihomoRulesInfo> => {
|
||||
const instance = await getAxios()
|
||||
return (await instance.get('/rules').catch(() => {
|
||||
try {
|
||||
return await instance.get('/rules')
|
||||
} catch (e) {
|
||||
return { rules: [] }
|
||||
})) as IMihomoRulesInfo
|
||||
}
|
||||
}
|
||||
|
||||
export const mihomoProxies = async (): Promise<IMihomoProxies> => {
|
||||
const instance = await getAxios()
|
||||
return (await instance.get('/proxies').catch(() => {
|
||||
try {
|
||||
return await instance.get('/proxies')
|
||||
} catch (e) {
|
||||
return { proxies: {} }
|
||||
})) as IMihomoProxies
|
||||
}
|
||||
}
|
||||
|
||||
export const mihomoProxyProviders = async (): Promise<IMihomoProxyProviders> => {
|
||||
const instance = await getAxios()
|
||||
return (await instance.get('/providers/proxies').catch(() => {
|
||||
try {
|
||||
return await instance.get('/providers/proxies')
|
||||
} catch (e) {
|
||||
return { providers: {} }
|
||||
})) as IMihomoProxyProviders
|
||||
}
|
||||
}
|
||||
|
||||
export const mihomoUpdateProxyProviders = async (name: string): Promise<void> => {
|
||||
const instance = await getAxios()
|
||||
return instance.put(`/providers/proxies/${encodeURIComponent(name)}`).catch((e) => {
|
||||
return e.response.data
|
||||
})
|
||||
return await instance.put(`/providers/proxies/${encodeURIComponent(name)}`)
|
||||
}
|
||||
|
||||
export const mihomoRuleProviders = async (): Promise<IMihomoRuleProviders> => {
|
||||
const instance = await getAxios()
|
||||
return (await instance.get('/providers/rules').catch(() => {
|
||||
try {
|
||||
return await instance.get('/providers/rules')
|
||||
} catch (e) {
|
||||
return { providers: {} }
|
||||
})) as IMihomoRuleProviders
|
||||
}
|
||||
}
|
||||
|
||||
export const mihomoUpdateRuleProviders = async (name: string): Promise<void> => {
|
||||
const instance = await getAxios()
|
||||
return instance.put(`/providers/rules/${encodeURIComponent(name)}`).catch((e) => {
|
||||
return e.response.data
|
||||
})
|
||||
return await instance.put(`/providers/rules/${encodeURIComponent(name)}`)
|
||||
}
|
||||
|
||||
export const mihomoChangeProxy = async (group: string, proxy: string): Promise<IMihomoProxy> => {
|
||||
const instance = await getAxios()
|
||||
return (await instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy }).catch(() => {
|
||||
return {
|
||||
alive: false,
|
||||
extra: {},
|
||||
history: [],
|
||||
id: '',
|
||||
name: '',
|
||||
tfo: false,
|
||||
type: 'Shadowsocks',
|
||||
udp: false,
|
||||
xudp: false
|
||||
}
|
||||
})) as IMihomoProxy
|
||||
return await instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy })
|
||||
}
|
||||
|
||||
export const mihomoUpgradeGeo = async (): Promise<void> => {
|
||||
const instance = await getAxios()
|
||||
return instance.post('/configs/geo').catch((e) => {
|
||||
return e.response.data
|
||||
})
|
||||
return await instance.post('/configs/geo')
|
||||
}
|
||||
|
||||
export const mihomoProxyDelay = async (proxy: string, url?: string): Promise<IMihomoDelay> => {
|
||||
const appConfig = getAppConfig()
|
||||
const appConfig = await getAppConfig()
|
||||
const { delayTestUrl, delayTestTimeout } = appConfig
|
||||
const instance = await getAxios()
|
||||
return (await instance
|
||||
.get(`/proxies/${encodeURIComponent(proxy)}/delay`, {
|
||||
return await instance.get(`/proxies/${encodeURIComponent(proxy)}/delay`, {
|
||||
params: {
|
||||
url: url || delayTestUrl || 'https://www.gstatic.com/generate_204',
|
||||
timeout: delayTestTimeout || 5000
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
return e.response.data
|
||||
})) as IMihomoDelay
|
||||
}
|
||||
|
||||
export const mihomoGroupDelay = async (group: string, url?: string): Promise<IMihomoGroupDelay> => {
|
||||
const appConfig = getAppConfig()
|
||||
const appConfig = await getAppConfig()
|
||||
const { delayTestUrl, delayTestTimeout } = appConfig
|
||||
const instance = await getAxios()
|
||||
return (await instance
|
||||
.get(`/group/${encodeURIComponent(group)}/delay`, {
|
||||
return await instance.get(`/group/${encodeURIComponent(group)}/delay`, {
|
||||
params: {
|
||||
url: url || delayTestUrl || 'https://www.gstatic.com/generate_204',
|
||||
timeout: delayTestTimeout || 5000
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
return e.response.data
|
||||
})) as IMihomoGroupDelay
|
||||
}
|
||||
|
||||
export const startMihomoTraffic = (): void => {
|
||||
mihomoTraffic()
|
||||
export const startMihomoTraffic = async (): Promise<void> => {
|
||||
await mihomoTraffic()
|
||||
}
|
||||
|
||||
export const stopMihomoTraffic = (): void => {
|
||||
|
@ -170,9 +159,10 @@ export const stopMihomoTraffic = (): void => {
|
|||
}
|
||||
}
|
||||
|
||||
const mihomoTraffic = (): void => {
|
||||
let server = getControledMihomoConfig()['external-controller']
|
||||
const secret = getControledMihomoConfig().secret ?? ''
|
||||
const mihomoTraffic = async (): Promise<void> => {
|
||||
const controledMihomoConfig = await getControledMihomoConfig()
|
||||
let server = controledMihomoConfig['external-controller']
|
||||
const secret = controledMihomoConfig.secret ?? ''
|
||||
if (server?.startsWith(':')) server = `127.0.0.1${server}`
|
||||
stopMihomoTraffic()
|
||||
|
||||
|
@ -181,7 +171,7 @@ const mihomoTraffic = (): void => {
|
|||
mihomoTrafficWs.onmessage = (e): void => {
|
||||
const data = e.data as string
|
||||
trafficRetry = 10
|
||||
window?.webContents.send('mihomoTraffic', JSON.parse(data) as IMihomoTrafficInfo)
|
||||
mainWindow?.webContents.send('mihomoTraffic', JSON.parse(data) as IMihomoTrafficInfo)
|
||||
}
|
||||
|
||||
mihomoTrafficWs.onclose = (): void => {
|
||||
|
@ -199,8 +189,8 @@ const mihomoTraffic = (): void => {
|
|||
}
|
||||
}
|
||||
|
||||
export const startMihomoMemory = (): void => {
|
||||
mihomoMemory()
|
||||
export const startMihomoMemory = async (): Promise<void> => {
|
||||
await mihomoMemory()
|
||||
}
|
||||
|
||||
export const stopMihomoMemory = (): void => {
|
||||
|
@ -213,9 +203,10 @@ export const stopMihomoMemory = (): void => {
|
|||
}
|
||||
}
|
||||
|
||||
const mihomoMemory = (): void => {
|
||||
let server = getControledMihomoConfig()['external-controller']
|
||||
const secret = getControledMihomoConfig().secret ?? ''
|
||||
const mihomoMemory = async (): Promise<void> => {
|
||||
const controledMihomoConfig = await getControledMihomoConfig()
|
||||
let server = controledMihomoConfig['external-controller']
|
||||
const secret = controledMihomoConfig.secret ?? ''
|
||||
if (server?.startsWith(':')) server = `127.0.0.1${server}`
|
||||
stopMihomoMemory()
|
||||
|
||||
|
@ -224,7 +215,7 @@ const mihomoMemory = (): void => {
|
|||
mihomoMemoryWs.onmessage = (e): void => {
|
||||
const data = e.data as string
|
||||
memoryRetry = 10
|
||||
window?.webContents.send('mihomoMemory', JSON.parse(data) as IMihomoMemoryInfo)
|
||||
mainWindow?.webContents.send('mihomoMemory', JSON.parse(data) as IMihomoMemoryInfo)
|
||||
}
|
||||
|
||||
mihomoMemoryWs.onclose = (): void => {
|
||||
|
@ -242,8 +233,8 @@ const mihomoMemory = (): void => {
|
|||
}
|
||||
}
|
||||
|
||||
export const startMihomoLogs = (): void => {
|
||||
mihomoLogs()
|
||||
export const startMihomoLogs = async (): Promise<void> => {
|
||||
await mihomoLogs()
|
||||
}
|
||||
|
||||
export const stopMihomoLogs = (): void => {
|
||||
|
@ -256,9 +247,10 @@ export const stopMihomoLogs = (): void => {
|
|||
}
|
||||
}
|
||||
|
||||
const mihomoLogs = (): void => {
|
||||
const { secret = '', 'log-level': level = 'info' } = getControledMihomoConfig()
|
||||
let { 'external-controller': server } = getControledMihomoConfig()
|
||||
const mihomoLogs = async (): Promise<void> => {
|
||||
const controledMihomoConfig = await getControledMihomoConfig()
|
||||
const { secret = '', 'log-level': level = 'info' } = controledMihomoConfig
|
||||
let { 'external-controller': server } = controledMihomoConfig
|
||||
if (server?.startsWith(':')) server = `127.0.0.1${server}`
|
||||
stopMihomoLogs()
|
||||
|
||||
|
@ -269,7 +261,7 @@ const mihomoLogs = (): void => {
|
|||
mihomoLogsWs.onmessage = (e): void => {
|
||||
const data = e.data as string
|
||||
logsRetry = 10
|
||||
window?.webContents.send('mihomoLogs', JSON.parse(data) as IMihomoLogInfo)
|
||||
mainWindow?.webContents.send('mihomoLogs', JSON.parse(data) as IMihomoLogInfo)
|
||||
}
|
||||
|
||||
mihomoLogsWs.onclose = (): void => {
|
||||
|
@ -287,8 +279,8 @@ const mihomoLogs = (): void => {
|
|||
}
|
||||
}
|
||||
|
||||
export const startMihomoConnections = (): void => {
|
||||
mihomoConnections()
|
||||
export const startMihomoConnections = async (): Promise<void> => {
|
||||
await mihomoConnections()
|
||||
}
|
||||
|
||||
export const stopMihomoConnections = (): void => {
|
||||
|
@ -301,9 +293,10 @@ export const stopMihomoConnections = (): void => {
|
|||
}
|
||||
}
|
||||
|
||||
const mihomoConnections = (): void => {
|
||||
let server = getControledMihomoConfig()['external-controller']
|
||||
const secret = getControledMihomoConfig().secret ?? ''
|
||||
const mihomoConnections = async (): Promise<void> => {
|
||||
const controledMihomoConfig = await getControledMihomoConfig()
|
||||
let server = controledMihomoConfig['external-controller']
|
||||
const secret = controledMihomoConfig.secret ?? ''
|
||||
if (server?.startsWith(':')) server = `127.0.0.1${server}`
|
||||
stopMihomoConnections()
|
||||
|
||||
|
@ -314,7 +307,7 @@ const mihomoConnections = (): void => {
|
|||
mihomoConnectionsWs.onmessage = (e): void => {
|
||||
const data = e.data as string
|
||||
connectionsRetry = 10
|
||||
window?.webContents.send('mihomoConnections', JSON.parse(data) as IMihomoConnectionsInfo)
|
||||
mainWindow?.webContents.send('mihomoConnections', JSON.parse(data) as IMihomoConnectionsInfo)
|
||||
}
|
||||
|
||||
mihomoConnectionsWs.onclose = (): void => {
|
||||
|
|
|
@ -1,42 +1,60 @@
|
|||
import { addProfileItem, getCurrentProfileItem, getProfileConfig, getProfileItem } from '../config'
|
||||
import { addProfileItem, getCurrentProfileItem, getProfileConfig } from '../config'
|
||||
|
||||
const intervalPool: Record<string, NodeJS.Timeout> = {}
|
||||
|
||||
export async function initProfileUpdater(): Promise<void> {
|
||||
const { items, current } = getProfileConfig()
|
||||
const currentItem = getCurrentProfileItem()
|
||||
const { items, current } = await getProfileConfig()
|
||||
const currentItem = await getCurrentProfileItem()
|
||||
for (const item of items.filter((i) => i.id !== current)) {
|
||||
if (item.type === 'remote' && item.interval) {
|
||||
await addProfileItem(item)
|
||||
intervalPool[item.id] = setInterval(
|
||||
intervalPool[item.id] = setTimeout(
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
item.interval * 60 * 1000
|
||||
)
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
if (currentItem.type === 'remote' && currentItem.interval) {
|
||||
await addProfileItem(currentItem)
|
||||
intervalPool[currentItem.id] = setInterval(
|
||||
}
|
||||
if (currentItem?.type === 'remote' && currentItem.interval) {
|
||||
intervalPool[currentItem.id] = setTimeout(
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
currentItem.interval * 60 * 1000 + 10000 // +10s
|
||||
)
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addProfileUpdater(id: string): void {
|
||||
const item = getProfileItem(id)
|
||||
|
||||
export async function addProfileUpdater(item: IProfileItem): Promise<void> {
|
||||
if (item.type === 'remote' && item.interval) {
|
||||
if (intervalPool[id]) {
|
||||
clearInterval(intervalPool[id])
|
||||
if (intervalPool[item.id]) {
|
||||
clearTimeout(intervalPool[item.id])
|
||||
}
|
||||
intervalPool[id] = setInterval(
|
||||
intervalPool[item.id] = setTimeout(
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
},
|
||||
item.interval * 60 * 1000
|
||||
)
|
||||
|
|
|
@ -1,98 +1,100 @@
|
|||
import {
|
||||
getAppConfig,
|
||||
getControledMihomoConfig,
|
||||
setAppConfig,
|
||||
setControledMihomoConfig
|
||||
patchAppConfig,
|
||||
patchControledMihomoConfig
|
||||
} from '../config'
|
||||
import icoIcon from '../../../resources/icon.ico?asset'
|
||||
import pngIcon from '../../../resources/icon.png?asset'
|
||||
import { patchMihomoConfig } from './mihomoApi'
|
||||
import { window } from '..'
|
||||
import { mainWindow } from '..'
|
||||
import { app, ipcMain, Menu, shell, Tray } from 'electron'
|
||||
import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs'
|
||||
import { triggerSysProxy } from '../resolve/sysproxy'
|
||||
|
||||
let tray: Tray | null = null
|
||||
|
||||
const buildContextMenu = (): Menu => {
|
||||
const buildContextMenu = async (): Promise<Menu> => {
|
||||
const { mode, tun } = await getControledMihomoConfig()
|
||||
const { sysProxy } = await getAppConfig()
|
||||
const contextMenu = [
|
||||
{
|
||||
id: 'show',
|
||||
label: '显示窗口',
|
||||
type: 'normal',
|
||||
click: (): void => {
|
||||
window?.show()
|
||||
window?.focusOnWebView()
|
||||
mainWindow?.show()
|
||||
mainWindow?.focusOnWebView()
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rule',
|
||||
label: '规则模式',
|
||||
type: 'radio',
|
||||
checked: getControledMihomoConfig().mode === 'rule',
|
||||
click: (): void => {
|
||||
setControledMihomoConfig({ mode: 'rule' })
|
||||
patchMihomoConfig({ mode: 'rule' })
|
||||
window?.webContents.send('controledMihomoConfigUpdated')
|
||||
updateTrayMenu()
|
||||
checked: mode === 'rule',
|
||||
click: async (): Promise<void> => {
|
||||
await patchControledMihomoConfig({ mode: 'rule' })
|
||||
await patchMihomoConfig({ mode: 'rule' })
|
||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
await updateTrayMenu()
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'global',
|
||||
label: '全局模式',
|
||||
type: 'radio',
|
||||
checked: getControledMihomoConfig().mode === 'global',
|
||||
click: (): void => {
|
||||
setControledMihomoConfig({ mode: 'global' })
|
||||
patchMihomoConfig({ mode: 'global' })
|
||||
window?.webContents.send('controledMihomoConfigUpdated')
|
||||
updateTrayMenu()
|
||||
checked: mode === 'global',
|
||||
click: async (): Promise<void> => {
|
||||
await patchControledMihomoConfig({ mode: 'global' })
|
||||
await patchMihomoConfig({ mode: 'global' })
|
||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
await updateTrayMenu()
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'direct',
|
||||
label: '直连模式',
|
||||
type: 'radio',
|
||||
checked: getControledMihomoConfig().mode === 'direct',
|
||||
click: (): void => {
|
||||
setControledMihomoConfig({ mode: 'direct' })
|
||||
patchMihomoConfig({ mode: 'direct' })
|
||||
window?.webContents.send('controledMihomoConfigUpdated')
|
||||
updateTrayMenu()
|
||||
checked: mode === 'direct',
|
||||
click: async (): Promise<void> => {
|
||||
await patchControledMihomoConfig({ mode: 'direct' })
|
||||
await patchMihomoConfig({ mode: 'direct' })
|
||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
await updateTrayMenu()
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
type: 'checkbox',
|
||||
label: '系统代理',
|
||||
checked: getAppConfig().sysProxy?.enable ?? false,
|
||||
click: (item): void => {
|
||||
checked: sysProxy.enable,
|
||||
click: async (item): Promise<void> => {
|
||||
const enable = item.checked
|
||||
try {
|
||||
await patchAppConfig({ sysProxy: { enable } })
|
||||
triggerSysProxy(enable)
|
||||
setAppConfig({ sysProxy: { enable } })
|
||||
window?.webContents.send('appConfigUpdated')
|
||||
} catch (e) {
|
||||
setAppConfig({ sysProxy: { enable: !enable } })
|
||||
await patchAppConfig({ sysProxy: { enable: !enable } })
|
||||
} finally {
|
||||
updateTrayMenu()
|
||||
mainWindow?.webContents.send('appConfigUpdated')
|
||||
await updateTrayMenu()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
label: '虚拟网卡',
|
||||
checked: getControledMihomoConfig().tun?.enable ?? false,
|
||||
click: (item): void => {
|
||||
checked: tun?.enable ?? false,
|
||||
click: async (item): Promise<void> => {
|
||||
const enable = item.checked
|
||||
if (enable) {
|
||||
setControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||
} else {
|
||||
setControledMihomoConfig({ tun: { enable } })
|
||||
await patchControledMihomoConfig({ tun: { enable } })
|
||||
}
|
||||
patchMihomoConfig({ tun: { enable } })
|
||||
window?.webContents.send('controledMihomoConfigUpdated')
|
||||
updateTrayMenu()
|
||||
await patchMihomoConfig({ tun: { enable } })
|
||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
await updateTrayMenu()
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
|
@ -137,19 +139,19 @@ const buildContextMenu = (): Menu => {
|
|||
return Menu.buildFromTemplate(contextMenu)
|
||||
}
|
||||
|
||||
export function createTray(): void {
|
||||
export async function createTray(): Promise<void> {
|
||||
if (process.platform === 'linux') {
|
||||
tray = new Tray(pngIcon)
|
||||
} else {
|
||||
tray = new Tray(icoIcon)
|
||||
}
|
||||
const menu = buildContextMenu()
|
||||
const menu = await buildContextMenu()
|
||||
|
||||
ipcMain.on('controledMihomoConfigUpdated', () => {
|
||||
updateTrayMenu()
|
||||
ipcMain.on('controledMihomoConfigUpdated', async () => {
|
||||
await updateTrayMenu()
|
||||
})
|
||||
ipcMain.on('appConfigUpdated', () => {
|
||||
updateTrayMenu()
|
||||
ipcMain.on('appConfigUpdated', async () => {
|
||||
await updateTrayMenu()
|
||||
})
|
||||
|
||||
tray.setContextMenu(menu)
|
||||
|
@ -157,11 +159,11 @@ export function createTray(): void {
|
|||
tray.setToolTip('Another Mihomo GUI.')
|
||||
tray.setTitle('Mihomo Party')
|
||||
tray.addListener('click', () => {
|
||||
window?.isVisible() ? window?.hide() : window?.show()
|
||||
mainWindow?.isVisible() ? mainWindow?.hide() : mainWindow?.show()
|
||||
})
|
||||
}
|
||||
|
||||
function updateTrayMenu(): void {
|
||||
const menu = buildContextMenu()
|
||||
async function updateTrayMenu(): Promise<void> {
|
||||
const menu = await buildContextMenu()
|
||||
tray?.setContextMenu(menu) // 更新菜单
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import { registerIpcMainHandlers } from './utils/ipc'
|
||||
import { app, shell, BrowserWindow, Menu } from 'electron'
|
||||
import { stopCore, startCore } from './core/manager'
|
||||
import { app, shell, BrowserWindow, Menu, dialog } from 'electron'
|
||||
import { stopCore } from './core/manager'
|
||||
import { triggerSysProxy } from './resolve/sysproxy'
|
||||
import icon from '../../resources/icon.png?asset'
|
||||
import { createTray } from './core/tray'
|
||||
|
@ -14,28 +14,29 @@ import {
|
|||
stopMihomoMemory,
|
||||
stopMihomoTraffic
|
||||
} from './core/mihomoApi'
|
||||
import { initProfileUpdater } from './core/profileUpdater'
|
||||
|
||||
export let window: BrowserWindow | null = null
|
||||
export let mainWindow: BrowserWindow | null = null
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
init()
|
||||
app.on('second-instance', (_event, commandline) => {
|
||||
window?.show()
|
||||
window?.focusOnWebView()
|
||||
}
|
||||
const initPromise = init()
|
||||
|
||||
app.on('second-instance', async (_event, commandline) => {
|
||||
mainWindow?.show()
|
||||
mainWindow?.focusOnWebView()
|
||||
const url = commandline.pop()
|
||||
if (url) {
|
||||
handleDeepLink(url)
|
||||
await handleDeepLink(url)
|
||||
}
|
||||
})
|
||||
app.on('open-url', (_event, url) => {
|
||||
window?.show()
|
||||
window?.focusOnWebView()
|
||||
handleDeepLink(url)
|
||||
|
||||
app.on('open-url', async (_event, url) => {
|
||||
mainWindow?.show()
|
||||
mainWindow?.focusOnWebView()
|
||||
await handleDeepLink(url)
|
||||
})
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
|
@ -55,14 +56,16 @@ if (!gotTheLock) {
|
|||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
app.whenReady().then(async () => {
|
||||
// Set app user model id for windows
|
||||
electronApp.setAppUserModelId('party.mihomo.app')
|
||||
startCore().then(() => {
|
||||
setTimeout(async () => {
|
||||
await initProfileUpdater()
|
||||
}, 60000)
|
||||
})
|
||||
try {
|
||||
await initPromise
|
||||
} catch (e) {
|
||||
dialog.showErrorBox('应用初始化失败', `${e}`)
|
||||
app.quit()
|
||||
}
|
||||
|
||||
// Default open or close DevTools by F12 in development
|
||||
// and ignore CommandOrControl + R in production.
|
||||
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
|
||||
|
@ -71,19 +74,18 @@ if (!gotTheLock) {
|
|||
})
|
||||
registerIpcMainHandlers()
|
||||
createWindow()
|
||||
createTray()
|
||||
await createTray()
|
||||
app.on('activate', function () {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function handleDeepLink(url: string): void {
|
||||
async function handleDeepLink(url: string): Promise<void> {
|
||||
if (url.startsWith('clash://install-config')) {
|
||||
url = url.replace('clash://install-config/?url=', '').replace('clash://install-config?url=', '')
|
||||
addProfileItem({
|
||||
await addProfileItem({
|
||||
type: 'remote',
|
||||
name: 'Remote File',
|
||||
url
|
||||
|
@ -93,7 +95,7 @@ function handleDeepLink(url: string): void {
|
|||
url = url
|
||||
.replace('mihomo://install-config/?url=', '')
|
||||
.replace('mihomo://install-config?url=', '')
|
||||
addProfileItem({
|
||||
await addProfileItem({
|
||||
type: 'remote',
|
||||
name: 'Remote File',
|
||||
url
|
||||
|
@ -104,7 +106,7 @@ function handleDeepLink(url: string): void {
|
|||
function createWindow(): void {
|
||||
Menu.setApplicationMenu(null)
|
||||
// Create the browser window.
|
||||
window = new BrowserWindow({
|
||||
mainWindow = new BrowserWindow({
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
width: 800,
|
||||
|
@ -118,31 +120,32 @@ function createWindow(): void {
|
|||
sandbox: false
|
||||
}
|
||||
})
|
||||
window.on('ready-to-show', () => {
|
||||
if (!getAppConfig().silentStart) {
|
||||
window?.show()
|
||||
window?.focusOnWebView()
|
||||
mainWindow.on('ready-to-show', async () => {
|
||||
const { silentStart } = await getAppConfig()
|
||||
if (!silentStart) {
|
||||
mainWindow?.show()
|
||||
mainWindow?.focusOnWebView()
|
||||
}
|
||||
})
|
||||
|
||||
window.on('resize', () => {
|
||||
window?.webContents.send('resize')
|
||||
mainWindow.on('resize', () => {
|
||||
mainWindow?.webContents.send('resize')
|
||||
})
|
||||
|
||||
window.on('show', () => {
|
||||
mainWindow.on('show', () => {
|
||||
startMihomoTraffic()
|
||||
startMihomoMemory()
|
||||
})
|
||||
|
||||
window.on('close', (event) => {
|
||||
mainWindow.on('close', (event) => {
|
||||
stopMihomoTraffic()
|
||||
stopMihomoMemory()
|
||||
event.preventDefault()
|
||||
window?.hide()
|
||||
window?.webContents.reload()
|
||||
mainWindow?.hide()
|
||||
mainWindow?.webContents.reload()
|
||||
})
|
||||
|
||||
window.webContents.setWindowOpenHandler((details) => {
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
@ -150,8 +153,8 @@ function createWindow(): void {
|
|||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
window.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||
} else {
|
||||
window.loadFile(join(__dirname, '../renderer/index.html'))
|
||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { exec } from 'child_process'
|
||||
import { exePath } from '../utils/dirs'
|
||||
import { dataDir, exePath, homeDir } from '../utils/dirs'
|
||||
import { mkdir, readFile, rm, writeFile } from 'fs/promises'
|
||||
import { existsSync } from 'fs'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs'
|
||||
import { promisify } from 'util'
|
||||
import path from 'path'
|
||||
|
||||
const appName = 'mihomo-party'
|
||||
|
||||
|
@ -51,12 +54,13 @@ const taskXml = `
|
|||
|
||||
export async function checkAutoRun(): Promise<boolean> {
|
||||
if (process.platform === 'win32') {
|
||||
const { stdout } = (await new Promise((resolve) => {
|
||||
exec(`schtasks /query /tn "${appName}"`, (_err, stdout, stderr) => {
|
||||
resolve({ stdout, stderr })
|
||||
})
|
||||
})) as { stdout: string; stderr: string }
|
||||
const execPromise = promisify(exec)
|
||||
try {
|
||||
const { stdout } = await execPromise(`schtasks /query /tn "${appName}"`)
|
||||
return stdout.includes(appName)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
|
@ -64,16 +68,17 @@ export async function checkAutoRun(): Promise<boolean> {
|
|||
}
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
return fs.existsSync(`${app.getPath('home')}/.config/autostart/${appName}.desktop`)
|
||||
return existsSync(path.join(homeDir, '.config', 'autostart', `${appName}.desktop`))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function enableAutoRun(): void {
|
||||
export async function enableAutoRun(): Promise<void> {
|
||||
if (process.platform === 'win32') {
|
||||
const taskFilePath = `${app.getPath('userData')}\\${appName}.xml`
|
||||
fs.writeFileSync(taskFilePath, taskXml)
|
||||
exec(`schtasks /create /tn "${appName}" /xml "${taskFilePath}" /f`)
|
||||
const execPromise = promisify(exec)
|
||||
const taskFilePath = path.join(dataDir, `${appName}.xml`)
|
||||
await writeFile(taskFilePath, taskXml)
|
||||
await execPromise(`schtasks /create /tn "${appName}" /xml "${taskFilePath}" /f`)
|
||||
}
|
||||
if (process.platform === 'darwin') {
|
||||
app.setLoginItemSettings({
|
||||
|
@ -93,22 +98,23 @@ StartupWMClass=mihomo-party
|
|||
Comment=Mihomo Party
|
||||
Categories=Utility;
|
||||
`
|
||||
try {
|
||||
if (fs.existsSync(`/usr/share/applications/${appName}.desktop`)) {
|
||||
desktop = fs.readFileSync(`/usr/share/applications/${appName}.desktop`, 'utf8')
|
||||
|
||||
if (existsSync(`/usr/share/applications/${appName}.desktop`)) {
|
||||
desktop = await readFile(`/usr/share/applications/${appName}.desktop`, 'utf8')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
const autostartDir = path.join(homeDir, '.config', 'autostart')
|
||||
if (!existsSync(autostartDir)) {
|
||||
await mkdir(autostartDir, { recursive: true })
|
||||
}
|
||||
fs.mkdirSync(`${app.getPath('home')}/.config/autostart`, { recursive: true })
|
||||
const desktopFilePath = `${app.getPath('home')}/.config/autostart/${appName}.desktop`
|
||||
fs.writeFileSync(desktopFilePath, desktop)
|
||||
const desktopFilePath = path.join(autostartDir, `${appName}.desktop`)
|
||||
await writeFile(desktopFilePath, desktop)
|
||||
}
|
||||
}
|
||||
|
||||
export function disableAutoRun(): void {
|
||||
export async function disableAutoRun(): Promise<void> {
|
||||
if (process.platform === 'win32') {
|
||||
exec(`schtasks /delete /tn "${appName}" /f`)
|
||||
const execPromise = promisify(exec)
|
||||
await execPromise(`schtasks /delete /tn "${appName}" /f`)
|
||||
}
|
||||
if (process.platform === 'darwin') {
|
||||
app.setLoginItemSettings({
|
||||
|
@ -116,11 +122,7 @@ export function disableAutoRun(): void {
|
|||
})
|
||||
}
|
||||
if (process.platform === 'linux') {
|
||||
const desktopFilePath = `${app.getPath('home')}/.config/autostart/${appName}.desktop`
|
||||
try {
|
||||
fs.rmSync(desktopFilePath)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
const desktopFilePath = path.join(homeDir, '.config', 'autostart', `${appName}.desktop`)
|
||||
await rm(desktopFilePath)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { app } from 'electron'
|
|||
import { getControledMihomoConfig } from '../config'
|
||||
|
||||
export async function checkUpdate(): Promise<string | undefined> {
|
||||
try {
|
||||
const res = await axios.get(
|
||||
'https://github.com/pompurin404/mihomo-party/releases/latest/download/latest.yml',
|
||||
{
|
||||
|
@ -16,14 +15,12 @@ export async function checkUpdate(): Promise<string | undefined> {
|
|||
}
|
||||
}
|
||||
)
|
||||
const latest = yaml.parse(res.data)
|
||||
const latest = yaml.parse(res.data) as { version: string }
|
||||
const remoteVersion = latest.version
|
||||
const currentVersion = app.getVersion()
|
||||
if (remoteVersion !== currentVersion) {
|
||||
return remoteVersion
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ import { mihomoWorkConfigPath } from '../utils/dirs'
|
|||
import yaml from 'yaml'
|
||||
import fs from 'fs'
|
||||
|
||||
export function generateProfile(): void {
|
||||
const current = getProfileConfig().current
|
||||
const currentProfile = overrideProfile(current, getProfile(current))
|
||||
const controledMihomoConfig = getControledMihomoConfig()
|
||||
export async function generateProfile(): Promise<void> {
|
||||
const { current } = await getProfileConfig()
|
||||
const currentProfile = await overrideProfile(current, await getProfile(current))
|
||||
const controledMihomoConfig = await getControledMihomoConfig()
|
||||
const { tun: profileTun = {} } = currentProfile
|
||||
const { tun: controledTun } = controledMihomoConfig
|
||||
const tun = Object.assign(profileTun, controledTun)
|
||||
|
@ -26,13 +26,24 @@ export function generateProfile(): void {
|
|||
profile.tun = tun
|
||||
profile.dns = dns
|
||||
profile.sniffer = sniffer
|
||||
fs.writeFileSync(mihomoWorkConfigPath(), yaml.stringify(profile))
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(mihomoWorkConfigPath(), yaml.stringify(profile), (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function overrideProfile(current: string | undefined, profile: IMihomoConfig): IMihomoConfig {
|
||||
const overrideScriptList = getProfileItem(current).override || []
|
||||
for (const override of overrideScriptList) {
|
||||
const script = getOverride(override)
|
||||
async function overrideProfile(
|
||||
current: string | undefined,
|
||||
profile: IMihomoConfig
|
||||
): Promise<IMihomoConfig> {
|
||||
const { override = [] } = (await getProfileItem(current)) || {}
|
||||
for (const ov of override) {
|
||||
const script = await getOverride(ov)
|
||||
profile = runOverrideScript(profile, script)
|
||||
}
|
||||
return profile
|
||||
|
@ -42,9 +53,7 @@ function runOverrideScript(profile: IMihomoConfig, script: string): IMihomoConfi
|
|||
try {
|
||||
const func = eval(`${script} main`)
|
||||
const newProfile = func(profile)
|
||||
if (typeof newProfile !== 'object') {
|
||||
throw new Error('Override script must return an object')
|
||||
}
|
||||
if (typeof newProfile !== 'object') return profile
|
||||
return newProfile
|
||||
} catch (e) {
|
||||
return profile
|
||||
|
|
|
@ -20,83 +20,98 @@ import {
|
|||
defaultProfileConfig
|
||||
} from '../utils/template'
|
||||
import yaml from 'yaml'
|
||||
import fs from 'fs'
|
||||
import { mkdir, writeFile, copyFile } from 'fs/promises'
|
||||
import { existsSync } from 'fs'
|
||||
import path from 'path'
|
||||
import { startPacServer } from './server'
|
||||
import { triggerSysProxy } from './sysproxy'
|
||||
import { getAppConfig } from '../config'
|
||||
import { app } from 'electron'
|
||||
import { startCore } from '../core/manager'
|
||||
import { initProfileUpdater } from '../core/profileUpdater'
|
||||
|
||||
function initDirs(): void {
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir)
|
||||
async function initDirs(): Promise<void> {
|
||||
if (!existsSync(dataDir)) {
|
||||
await mkdir(dataDir)
|
||||
}
|
||||
if (!fs.existsSync(profilesDir())) {
|
||||
fs.mkdirSync(profilesDir())
|
||||
if (!existsSync(profilesDir())) {
|
||||
await mkdir(profilesDir())
|
||||
}
|
||||
if (!fs.existsSync(overrideDir())) {
|
||||
fs.mkdirSync(overrideDir())
|
||||
if (!existsSync(overrideDir())) {
|
||||
await mkdir(overrideDir())
|
||||
}
|
||||
if (!fs.existsSync(mihomoWorkDir())) {
|
||||
fs.mkdirSync(mihomoWorkDir())
|
||||
if (!existsSync(mihomoWorkDir())) {
|
||||
await mkdir(mihomoWorkDir())
|
||||
}
|
||||
if (!fs.existsSync(logDir())) {
|
||||
fs.mkdirSync(logDir())
|
||||
if (!existsSync(logDir())) {
|
||||
await mkdir(logDir())
|
||||
}
|
||||
if (!fs.existsSync(mihomoTestDir())) {
|
||||
fs.mkdirSync(mihomoTestDir())
|
||||
if (!existsSync(mihomoTestDir())) {
|
||||
await mkdir(mihomoTestDir())
|
||||
}
|
||||
}
|
||||
|
||||
function initConfig(): void {
|
||||
if (!fs.existsSync(appConfigPath())) {
|
||||
fs.writeFileSync(appConfigPath(), yaml.stringify(defaultConfig))
|
||||
async function initConfig(): Promise<void> {
|
||||
if (!existsSync(appConfigPath())) {
|
||||
await writeFile(appConfigPath(), yaml.stringify(defaultConfig))
|
||||
}
|
||||
if (!fs.existsSync(profileConfigPath())) {
|
||||
fs.writeFileSync(profileConfigPath(), yaml.stringify(defaultProfileConfig))
|
||||
if (!existsSync(profileConfigPath())) {
|
||||
await writeFile(profileConfigPath(), yaml.stringify(defaultProfileConfig))
|
||||
}
|
||||
if (!fs.existsSync(overrideConfigPath())) {
|
||||
fs.writeFileSync(overrideConfigPath(), yaml.stringify(defaultOverrideConfig))
|
||||
if (!existsSync(overrideConfigPath())) {
|
||||
await writeFile(overrideConfigPath(), yaml.stringify(defaultOverrideConfig))
|
||||
}
|
||||
if (!fs.existsSync(profilePath('default'))) {
|
||||
fs.writeFileSync(profilePath('default'), yaml.stringify(defaultProfile))
|
||||
if (!existsSync(profilePath('default'))) {
|
||||
await writeFile(profilePath('default'), yaml.stringify(defaultProfile))
|
||||
}
|
||||
if (!fs.existsSync(controledMihomoConfigPath())) {
|
||||
fs.writeFileSync(controledMihomoConfigPath(), yaml.stringify(defaultControledMihomoConfig))
|
||||
if (!existsSync(controledMihomoConfigPath())) {
|
||||
await writeFile(controledMihomoConfigPath(), yaml.stringify(defaultControledMihomoConfig))
|
||||
}
|
||||
}
|
||||
|
||||
function initFiles(): void {
|
||||
const fileList = ['country.mmdb', 'geoip.dat', 'geosite.dat', 'ASN.mmdb']
|
||||
for (const file of fileList) {
|
||||
async function initFiles(): Promise<void> {
|
||||
const copy = async (file: string): Promise<void> => {
|
||||
const targetPath = path.join(mihomoWorkDir(), file)
|
||||
const testTargrtPath = path.join(mihomoTestDir(), file)
|
||||
const sourcePath = path.join(resourcesFilesDir(), file)
|
||||
if (!fs.existsSync(targetPath) && fs.existsSync(sourcePath)) {
|
||||
fs.copyFileSync(sourcePath, targetPath)
|
||||
if (!existsSync(targetPath) && existsSync(sourcePath)) {
|
||||
await copyFile(sourcePath, targetPath)
|
||||
}
|
||||
if (!fs.existsSync(testTargrtPath) && fs.existsSync(sourcePath)) {
|
||||
fs.copyFileSync(sourcePath, testTargrtPath)
|
||||
if (!existsSync(testTargrtPath) && existsSync(sourcePath)) {
|
||||
await copyFile(sourcePath, testTargrtPath)
|
||||
}
|
||||
}
|
||||
await Promise.all([
|
||||
copy('country.mmdb'),
|
||||
copy('geoip.dat'),
|
||||
copy('geosite.dat'),
|
||||
copy('ASN.mmdb')
|
||||
])
|
||||
}
|
||||
|
||||
function initDeeplink(): void {
|
||||
if (process.defaultApp) {
|
||||
if (process.argv.length >= 2) {
|
||||
app.setAsDefaultProtocolClient('clash', process.execPath, [path.resolve(process.argv[1])])
|
||||
app.setAsDefaultProtocolClient('mihomo', process.execPath, [path.resolve(process.argv[1])])
|
||||
}
|
||||
} else {
|
||||
app.setAsDefaultProtocolClient('clash')
|
||||
app.setAsDefaultProtocolClient('mihomo')
|
||||
}
|
||||
}
|
||||
|
||||
export function init(): void {
|
||||
initDirs()
|
||||
initConfig()
|
||||
initFiles()
|
||||
initDeeplink()
|
||||
startPacServer().then(() => {
|
||||
triggerSysProxy(getAppConfig().sysProxy.enable)
|
||||
export async function init(): Promise<void> {
|
||||
await initDirs()
|
||||
await initConfig()
|
||||
await initFiles()
|
||||
await startPacServer()
|
||||
const { sysProxy } = await getAppConfig()
|
||||
await triggerSysProxy(sysProxy.enable)
|
||||
startCore().then(() => {
|
||||
setTimeout(async () => {
|
||||
await initProfileUpdater()
|
||||
}, 60000)
|
||||
})
|
||||
initDeeplink()
|
||||
}
|
||||
|
|
|
@ -34,11 +34,11 @@ function findAvailablePort(startPort: number): Promise<number> {
|
|||
export async function startPacServer(): Promise<void> {
|
||||
pacPort = await findAvailablePort(10000)
|
||||
const server = http
|
||||
.createServer((_req, res) => {
|
||||
.createServer(async (_req, res) => {
|
||||
const {
|
||||
sysProxy: { pacScript }
|
||||
} = getAppConfig()
|
||||
const { 'mixed-port': port = 7890 } = getControledMihomoConfig()
|
||||
} = await getAppConfig()
|
||||
const { 'mixed-port': port = 7890 } = await getControledMihomoConfig()
|
||||
let script = pacScript || defaultPacScript
|
||||
script = script.replaceAll('%mixed-port%', port.toString())
|
||||
res.writeHead(200, { 'Content-Type': 'application/x-ns-proxy-autoconfig' })
|
||||
|
|
|
@ -42,19 +42,19 @@ if (process.platform === 'win32')
|
|||
'<local>'
|
||||
]
|
||||
|
||||
export function triggerSysProxy(enable: boolean): void {
|
||||
export async function triggerSysProxy(enable: boolean): Promise<void> {
|
||||
if (enable) {
|
||||
disableSysProxy()
|
||||
enableSysProxy()
|
||||
await enableSysProxy()
|
||||
} else {
|
||||
disableSysProxy()
|
||||
}
|
||||
}
|
||||
|
||||
export function enableSysProxy(): void {
|
||||
const { sysProxy } = getAppConfig()
|
||||
export async function enableSysProxy(): Promise<void> {
|
||||
const { sysProxy } = await getAppConfig()
|
||||
const { mode, host, bypass = defaultBypass } = sysProxy
|
||||
const { 'mixed-port': port = 7890 } = getControledMihomoConfig()
|
||||
const { 'mixed-port': port = 7890 } = await getControledMihomoConfig()
|
||||
|
||||
switch (mode || 'manual') {
|
||||
case 'auto': {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { app } from 'electron'
|
|||
import path from 'path'
|
||||
|
||||
export const dataDir = app.getPath('userData')
|
||||
export const homeDir = app.getPath('home')
|
||||
|
||||
export function exePath(): string {
|
||||
return app.getPath('exe')
|
||||
|
|
|
@ -22,9 +22,9 @@ import {
|
|||
import { checkAutoRun, disableAutoRun, enableAutoRun } from '../resolve/autoRun'
|
||||
import {
|
||||
getAppConfig,
|
||||
setAppConfig,
|
||||
patchAppConfig,
|
||||
getControledMihomoConfig,
|
||||
setControledMihomoConfig,
|
||||
patchControledMihomoConfig,
|
||||
getProfileConfig,
|
||||
getCurrentProfileItem,
|
||||
getProfileItem,
|
||||
|
@ -48,68 +48,95 @@ import { isEncryptionAvailable, restartCore } from '../core/manager'
|
|||
import { triggerSysProxy } from '../resolve/sysproxy'
|
||||
import { checkUpdate } from '../resolve/autoUpdater'
|
||||
import { exePath, mihomoCorePath, mihomoWorkConfigPath, resourcesDir } from './dirs'
|
||||
import { execFile, execSync } from 'child_process'
|
||||
import { exec, execFile } from 'child_process'
|
||||
import yaml from 'yaml'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { promisify } from 'util'
|
||||
import { readFile } from 'fs/promises'
|
||||
|
||||
function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fn: (...args: any[]) => Promise<T> // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): (...args: any[]) => Promise<T | { invokeError: unknown }> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return async (...args: any[]) => {
|
||||
try {
|
||||
return await fn(...args)
|
||||
} catch (e) {
|
||||
return { invokeError: `${e}` }
|
||||
}
|
||||
}
|
||||
}
|
||||
export function registerIpcMainHandlers(): void {
|
||||
ipcMain.handle('mihomoVersion', mihomoVersion)
|
||||
ipcMain.handle('mihomoCloseConnection', (_e, id) => mihomoCloseConnection(id))
|
||||
ipcMain.handle('mihomoCloseAllConnections', mihomoCloseAllConnections)
|
||||
ipcMain.handle('mihomoRules', mihomoRules)
|
||||
ipcMain.handle('mihomoProxies', mihomoProxies)
|
||||
ipcMain.handle('mihomoProxyProviders', () => mihomoProxyProviders())
|
||||
ipcMain.handle('mihomoUpdateProxyProviders', (_e, name) => mihomoUpdateProxyProviders(name))
|
||||
ipcMain.handle('mihomoRuleProviders', () => mihomoRuleProviders())
|
||||
ipcMain.handle('mihomoUpdateRuleProviders', (_e, name) => mihomoUpdateRuleProviders(name))
|
||||
ipcMain.handle('mihomoChangeProxy', (_e, group, proxy) => mihomoChangeProxy(group, proxy))
|
||||
ipcMain.handle('mihomoUpgradeGeo', mihomoUpgradeGeo)
|
||||
ipcMain.handle('mihomoProxyDelay', (_e, proxy, url) => mihomoProxyDelay(proxy, url))
|
||||
ipcMain.handle('mihomoGroupDelay', (_e, group, url) => mihomoGroupDelay(group, url))
|
||||
ipcMain.handle('startMihomoLogs', startMihomoLogs)
|
||||
ipcMain.handle('mihomoVersion', ipcErrorWrapper(mihomoVersion))
|
||||
ipcMain.handle('mihomoCloseConnection', (_e, id) => ipcErrorWrapper(mihomoCloseConnection)(id))
|
||||
ipcMain.handle('mihomoCloseAllConnections', ipcErrorWrapper(mihomoCloseAllConnections))
|
||||
ipcMain.handle('mihomoRules', ipcErrorWrapper(mihomoRules))
|
||||
ipcMain.handle('mihomoProxies', ipcErrorWrapper(mihomoProxies))
|
||||
ipcMain.handle('mihomoProxyProviders', ipcErrorWrapper(mihomoProxyProviders))
|
||||
ipcMain.handle('mihomoUpdateProxyProviders', (_e, name) =>
|
||||
ipcErrorWrapper(mihomoUpdateProxyProviders)(name)
|
||||
)
|
||||
ipcMain.handle('mihomoRuleProviders', ipcErrorWrapper(mihomoRuleProviders))
|
||||
ipcMain.handle('mihomoUpdateRuleProviders', (_e, name) =>
|
||||
ipcErrorWrapper(mihomoUpdateRuleProviders)(name)
|
||||
)
|
||||
ipcMain.handle('mihomoChangeProxy', (_e, group, proxy) =>
|
||||
ipcErrorWrapper(mihomoChangeProxy)(group, proxy)
|
||||
)
|
||||
ipcMain.handle('mihomoUpgradeGeo', ipcErrorWrapper(mihomoUpgradeGeo))
|
||||
ipcMain.handle('mihomoProxyDelay', (_e, proxy, url) =>
|
||||
ipcErrorWrapper(mihomoProxyDelay)(proxy, url)
|
||||
)
|
||||
ipcMain.handle('mihomoGroupDelay', (_e, group, url) =>
|
||||
ipcErrorWrapper(mihomoGroupDelay)(group, url)
|
||||
)
|
||||
ipcMain.handle('startMihomoLogs', ipcErrorWrapper(startMihomoLogs))
|
||||
ipcMain.handle('stopMihomoLogs', stopMihomoLogs)
|
||||
ipcMain.handle('startMihomoConnections', () => startMihomoConnections())
|
||||
ipcMain.handle('stopMihomoConnections', () => stopMihomoConnections())
|
||||
ipcMain.handle('patchMihomoConfig', (_e, patch) => patchMihomoConfig(patch))
|
||||
ipcMain.handle('checkAutoRun', checkAutoRun)
|
||||
ipcMain.handle('enableAutoRun', enableAutoRun)
|
||||
ipcMain.handle('disableAutoRun', disableAutoRun)
|
||||
ipcMain.handle('getAppConfig', (_e, force) => getAppConfig(force))
|
||||
ipcMain.handle('setAppConfig', (_e, config) => setAppConfig(config))
|
||||
ipcMain.handle('getControledMihomoConfig', (_e, force) => getControledMihomoConfig(force))
|
||||
ipcMain.handle('setControledMihomoConfig', (_e, config) => setControledMihomoConfig(config))
|
||||
ipcMain.handle('getProfileConfig', (_e, force) => getProfileConfig(force))
|
||||
ipcMain.handle('setProfileConfig', (_e, config) => setProfileConfig(config))
|
||||
ipcMain.handle('getCurrentProfileItem', getCurrentProfileItem)
|
||||
ipcMain.handle('getProfileItem', (_e, id) => getProfileItem(id))
|
||||
ipcMain.handle('getProfileStr', (_e, id) => getProfileStr(id))
|
||||
ipcMain.handle('setProfileStr', (_e, id, str) => setProfileStr(id, str))
|
||||
ipcMain.handle('updateProfileItem', (_e, item) => updateProfileItem(item))
|
||||
ipcMain.handle('changeCurrentProfile', (_e, id) => changeCurrentProfile(id))
|
||||
ipcMain.handle('addProfileItem', (_e, item) => addProfileItem(item))
|
||||
ipcMain.handle('removeProfileItem', (_e, id) => removeProfileItem(id))
|
||||
ipcMain.handle('getOverrideConfig', (_e, force) => getOverrideConfig(force))
|
||||
ipcMain.handle('setOverrideConfig', (_e, config) => setOverrideConfig(config))
|
||||
ipcMain.handle('getOverrideItem', (_e, id) => getOverrideItem(id))
|
||||
ipcMain.handle('addOverrideItem', (_e, item) => addOverrideItem(item))
|
||||
ipcMain.handle('removeOverrideItem', (_e, id) => removeOverrideItem(id))
|
||||
ipcMain.handle('updateOverrideItem', (_e, item) => updateOverrideItem(item))
|
||||
ipcMain.handle('getOverride', (_e, id) => getOverride(id))
|
||||
ipcMain.handle('setOverride', (_e, id, str) => setOverride(id, str))
|
||||
ipcMain.handle('restartCore', restartCore)
|
||||
ipcMain.handle('triggerSysProxy', (_e, enable) => triggerSysProxy(enable))
|
||||
ipcMain.handle('startMihomoConnections', ipcErrorWrapper(startMihomoConnections))
|
||||
ipcMain.handle('stopMihomoConnections', stopMihomoConnections)
|
||||
ipcMain.handle('patchMihomoConfig', (_e, patch) => ipcErrorWrapper(patchMihomoConfig)(patch))
|
||||
ipcMain.handle('checkAutoRun', ipcErrorWrapper(checkAutoRun))
|
||||
ipcMain.handle('enableAutoRun', ipcErrorWrapper(enableAutoRun))
|
||||
ipcMain.handle('disableAutoRun', ipcErrorWrapper(disableAutoRun))
|
||||
ipcMain.handle('getAppConfig', (_e, force) => ipcErrorWrapper(getAppConfig)(force))
|
||||
ipcMain.handle('patchAppConfig', (_e, config) => ipcErrorWrapper(patchAppConfig)(config))
|
||||
ipcMain.handle('getControledMihomoConfig', (_e, force) =>
|
||||
ipcErrorWrapper(getControledMihomoConfig)(force)
|
||||
)
|
||||
ipcMain.handle('patchControledMihomoConfig', (_e, config) =>
|
||||
ipcErrorWrapper(patchControledMihomoConfig)(config)
|
||||
)
|
||||
ipcMain.handle('getProfileConfig', (_e, force) => ipcErrorWrapper(getProfileConfig)(force))
|
||||
ipcMain.handle('setProfileConfig', (_e, config) => ipcErrorWrapper(setProfileConfig)(config))
|
||||
ipcMain.handle('getCurrentProfileItem', ipcErrorWrapper(getCurrentProfileItem))
|
||||
ipcMain.handle('getProfileItem', (_e, id) => ipcErrorWrapper(getProfileItem)(id))
|
||||
ipcMain.handle('getProfileStr', (_e, id) => ipcErrorWrapper(getProfileStr)(id))
|
||||
ipcMain.handle('setProfileStr', (_e, id, str) => ipcErrorWrapper(setProfileStr)(id, str))
|
||||
ipcMain.handle('updateProfileItem', (_e, item) => ipcErrorWrapper(updateProfileItem)(item))
|
||||
ipcMain.handle('changeCurrentProfile', (_e, id) => ipcErrorWrapper(changeCurrentProfile)(id))
|
||||
ipcMain.handle('addProfileItem', (_e, item) => ipcErrorWrapper(addProfileItem)(item))
|
||||
ipcMain.handle('removeProfileItem', (_e, id) => ipcErrorWrapper(removeProfileItem)(id))
|
||||
ipcMain.handle('getOverrideConfig', (_e, force) => ipcErrorWrapper(getOverrideConfig)(force))
|
||||
ipcMain.handle('setOverrideConfig', (_e, config) => ipcErrorWrapper(setOverrideConfig)(config))
|
||||
ipcMain.handle('getOverrideItem', (_e, id) => ipcErrorWrapper(getOverrideItem)(id))
|
||||
ipcMain.handle('addOverrideItem', (_e, item) => ipcErrorWrapper(addOverrideItem)(item))
|
||||
ipcMain.handle('removeOverrideItem', (_e, id) => ipcErrorWrapper(removeOverrideItem)(id))
|
||||
ipcMain.handle('updateOverrideItem', (_e, item) => ipcErrorWrapper(updateOverrideItem)(item))
|
||||
ipcMain.handle('getOverride', (_e, id) => ipcErrorWrapper(getOverride)(id))
|
||||
ipcMain.handle('setOverride', (_e, id, str) => ipcErrorWrapper(setOverride)(id, str))
|
||||
ipcMain.handle('restartCore', ipcErrorWrapper(restartCore))
|
||||
ipcMain.handle('triggerSysProxy', (_e, enable) => ipcErrorWrapper(triggerSysProxy)(enable))
|
||||
ipcMain.handle('isEncryptionAvailable', isEncryptionAvailable)
|
||||
ipcMain.handle('encryptString', (_e, str) => safeStorage.encryptString(str))
|
||||
ipcMain.handle('getFilePath', (_e, ext) => getFilePath(ext))
|
||||
ipcMain.handle('readTextFile', (_e, filePath) => readTextFile(filePath))
|
||||
ipcMain.handle('getRuntimeConfigStr', getRuntimeConfigStr)
|
||||
ipcMain.handle('getRuntimeConfig', getRuntimeConfig)
|
||||
ipcMain.handle('checkUpdate', () => checkUpdate())
|
||||
ipcMain.handle('readTextFile', (_e, filePath) => ipcErrorWrapper(readTextFile)(filePath))
|
||||
ipcMain.handle('getRuntimeConfigStr', ipcErrorWrapper(getRuntimeConfigStr))
|
||||
ipcMain.handle('getRuntimeConfig', ipcErrorWrapper(getRuntimeConfig))
|
||||
ipcMain.handle('checkUpdate', ipcErrorWrapper(checkUpdate))
|
||||
ipcMain.handle('getVersion', () => app.getVersion())
|
||||
ipcMain.handle('platform', () => process.platform)
|
||||
ipcMain.handle('openUWPTool', openUWPTool)
|
||||
ipcMain.handle('setupFirewall', setupFirewall)
|
||||
ipcMain.handle('openUWPTool', ipcErrorWrapper(openUWPTool))
|
||||
ipcMain.handle('setupFirewall', ipcErrorWrapper(setupFirewall))
|
||||
ipcMain.handle('quitApp', () => app.quit())
|
||||
}
|
||||
|
||||
|
@ -121,26 +148,26 @@ function getFilePath(ext: string[]): string[] | undefined {
|
|||
})
|
||||
}
|
||||
|
||||
function readTextFile(filePath: string): string {
|
||||
return fs.readFileSync(filePath, 'utf8')
|
||||
async function readTextFile(filePath: string): Promise<string> {
|
||||
return await readFile(filePath, 'utf8')
|
||||
}
|
||||
|
||||
function getRuntimeConfigStr(): string {
|
||||
return fs.readFileSync(mihomoWorkConfigPath(), 'utf8')
|
||||
async function getRuntimeConfigStr(): Promise<string> {
|
||||
return readFile(mihomoWorkConfigPath(), 'utf8')
|
||||
}
|
||||
|
||||
function getRuntimeConfig(): IMihomoConfig {
|
||||
return yaml.parse(getRuntimeConfigStr())
|
||||
async function getRuntimeConfig(): Promise<IMihomoConfig> {
|
||||
return yaml.parse(await getRuntimeConfigStr())
|
||||
}
|
||||
|
||||
function openUWPTool(): void {
|
||||
async function openUWPTool(): Promise<void> {
|
||||
const execFilePromise = promisify(execFile)
|
||||
const uwpToolPath = path.join(resourcesDir(), 'files', 'enableLoopback.exe')
|
||||
const child = execFile(uwpToolPath)
|
||||
child.unref()
|
||||
await execFilePromise(uwpToolPath)
|
||||
}
|
||||
|
||||
async function setupFirewall(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const execPromise = promisify(exec)
|
||||
const removeCommand = `
|
||||
Remove-NetFirewallRule -DisplayName "mihomo" -ErrorAction SilentlyContinue
|
||||
Remove-NetFirewallRule -DisplayName "mihomo-alpha" -ErrorAction SilentlyContinue
|
||||
|
@ -153,19 +180,7 @@ async function setupFirewall(): Promise<void> {
|
|||
`
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
execSync(removeCommand, { shell: 'powershell' })
|
||||
} catch {
|
||||
console.error('Remove-NetFirewallRule Failed')
|
||||
}
|
||||
try {
|
||||
execSync(createCommand, { shell: 'powershell' })
|
||||
} catch (e) {
|
||||
dialog.showErrorBox('防火墙设置失败', `${e}`)
|
||||
reject(e)
|
||||
console.error('New-NetFirewallRule Failed')
|
||||
await execPromise(removeCommand, { shell: 'powershell' })
|
||||
await execPromise(createCommand, { shell: 'powershell' })
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -179,7 +179,11 @@ const ProfileItem: React.FC<Props> = (props) => {
|
|||
disabled={updating}
|
||||
onPress={() => {
|
||||
setUpdating(true)
|
||||
addProfileItem(info).finally(() => {
|
||||
addProfileItem(info)
|
||||
.catch((e) => {
|
||||
alert(e)
|
||||
})
|
||||
.finally(() => {
|
||||
setUpdating(false)
|
||||
})
|
||||
}}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import useSWR from 'swr'
|
||||
import { getAppConfig, setAppConfig } from '@renderer/utils/ipc'
|
||||
import { getAppConfig, patchAppConfig as patch } from '@renderer/utils/ipc'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
interface RetuenType {
|
||||
|
@ -12,7 +12,7 @@ export const useAppConfig = (listenUpdate = false): RetuenType => {
|
|||
const { data: appConfig, mutate: mutateAppConfig } = useSWR('getConfig', () => getAppConfig())
|
||||
|
||||
const patchAppConfig = async (value: Partial<IAppConfig>): Promise<void> => {
|
||||
await setAppConfig(value)
|
||||
await patch(value)
|
||||
mutateAppConfig()
|
||||
window.electron.ipcRenderer.send('appConfigUpdated')
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import useSWR from 'swr'
|
||||
import { getControledMihomoConfig, setControledMihomoConfig } from '@renderer/utils/ipc'
|
||||
import { getControledMihomoConfig, patchControledMihomoConfig as patch } from '@renderer/utils/ipc'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
interface RetuenType {
|
||||
|
@ -15,7 +15,7 @@ export const useControledMihomoConfig = (listenUpdate = false): RetuenType => {
|
|||
)
|
||||
|
||||
const patchControledMihomoConfig = async (value: Partial<IMihomoConfig>): Promise<void> => {
|
||||
await setControledMihomoConfig(value)
|
||||
await patch(value)
|
||||
mutateControledMihomoConfig()
|
||||
window.electron.ipcRenderer.send('controledMihomoConfigUpdated')
|
||||
}
|
||||
|
|
|
@ -165,7 +165,11 @@ const Profiles: React.FC = () => {
|
|||
updateProfileItem={updateProfileItem}
|
||||
info={item}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await changeCurrentProfile(item.id)
|
||||
} catch (e) {
|
||||
alert(e)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -1,223 +1,242 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function ipcErrorWrapper(response: any): any {
|
||||
if (typeof response === 'object' && 'invokeError' in response) {
|
||||
throw response.invokeError
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
export async function mihomoVersion(): Promise<IMihomoVersion> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoVersion')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoVersion'))
|
||||
}
|
||||
|
||||
export async function mihomoCloseConnection(id: string): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoCloseConnection', id)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoCloseConnection', id))
|
||||
}
|
||||
|
||||
export async function mihomoCloseAllConnections(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoCloseAllConnections')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoCloseAllConnections'))
|
||||
}
|
||||
|
||||
export async function mihomoRules(): Promise<IMihomoRulesInfo> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoRules')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoRules'))
|
||||
}
|
||||
|
||||
export async function mihomoProxies(): Promise<IMihomoProxies> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoProxies')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoProxies'))
|
||||
}
|
||||
|
||||
export async function mihomoProxyProviders(): Promise<IMihomoProxyProviders> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoProxyProviders')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoProxyProviders'))
|
||||
}
|
||||
|
||||
export async function mihomoUpdateProxyProviders(name: string): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoUpdateProxyProviders', name)
|
||||
return ipcErrorWrapper(
|
||||
await window.electron.ipcRenderer.invoke('mihomoUpdateProxyProviders', name)
|
||||
)
|
||||
}
|
||||
|
||||
export async function mihomoRuleProviders(): Promise<IMihomoRuleProviders> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoRuleProviders')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoRuleProviders'))
|
||||
}
|
||||
|
||||
export async function mihomoUpdateRuleProviders(name: string): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoUpdateRuleProviders', name)
|
||||
return ipcErrorWrapper(
|
||||
await window.electron.ipcRenderer.invoke('mihomoUpdateRuleProviders', name)
|
||||
)
|
||||
}
|
||||
|
||||
export async function mihomoChangeProxy(group: string, proxy: string): Promise<IMihomoProxy> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoChangeProxy', group, proxy)
|
||||
return ipcErrorWrapper(
|
||||
await window.electron.ipcRenderer.invoke('mihomoChangeProxy', group, proxy)
|
||||
)
|
||||
}
|
||||
|
||||
export async function mihomoUpgradeGeo(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoUpgradeGeo')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoUpgradeGeo'))
|
||||
}
|
||||
|
||||
export async function mihomoProxyDelay(proxy: string, url?: string): Promise<IMihomoDelay> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoProxyDelay', proxy, url)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoProxyDelay', proxy, url))
|
||||
}
|
||||
|
||||
export async function mihomoGroupDelay(group: string, url?: string): Promise<IMihomoGroupDelay> {
|
||||
return await window.electron.ipcRenderer.invoke('mihomoGroupDelay', group, url)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoGroupDelay', group, url))
|
||||
}
|
||||
|
||||
export async function startMihomoLogs(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('startMihomoLogs')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('startMihomoLogs'))
|
||||
}
|
||||
|
||||
export async function stopMihomoLogs(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('stopMihomoLogs')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('stopMihomoLogs'))
|
||||
}
|
||||
|
||||
export async function startMihomoConnections(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('startMihomoConnections')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('startMihomoConnections'))
|
||||
}
|
||||
|
||||
export async function stopMihomoConnections(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('stopMihomoConnections')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('stopMihomoConnections'))
|
||||
}
|
||||
|
||||
export async function patchMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('patchMihomoConfig', patch)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('patchMihomoConfig', patch))
|
||||
}
|
||||
|
||||
export async function checkAutoRun(): Promise<boolean> {
|
||||
return await window.electron.ipcRenderer.invoke('checkAutoRun')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkAutoRun'))
|
||||
}
|
||||
|
||||
export async function enableAutoRun(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('enableAutoRun')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('enableAutoRun'))
|
||||
}
|
||||
|
||||
export async function disableAutoRun(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('disableAutoRun')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('disableAutoRun'))
|
||||
}
|
||||
|
||||
export async function getAppConfig(force = false): Promise<IAppConfig> {
|
||||
return await window.electron.ipcRenderer.invoke('getAppConfig', force)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getAppConfig', force))
|
||||
}
|
||||
|
||||
export async function setAppConfig(patch: Partial<IAppConfig>): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('setAppConfig', patch)
|
||||
export async function patchAppConfig(patch: Partial<IAppConfig>): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('patchAppConfig', patch))
|
||||
}
|
||||
|
||||
export async function getControledMihomoConfig(force = false): Promise<Partial<IMihomoConfig>> {
|
||||
return await window.electron.ipcRenderer.invoke('getControledMihomoConfig', force)
|
||||
return ipcErrorWrapper(
|
||||
await window.electron.ipcRenderer.invoke('getControledMihomoConfig', force)
|
||||
)
|
||||
}
|
||||
|
||||
export async function setControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('setControledMihomoConfig', patch)
|
||||
export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
|
||||
return ipcErrorWrapper(
|
||||
await window.electron.ipcRenderer.invoke('patchControledMihomoConfig', patch)
|
||||
)
|
||||
}
|
||||
|
||||
export async function getProfileConfig(force = false): Promise<IProfileConfig> {
|
||||
return await window.electron.ipcRenderer.invoke('getProfileConfig', force)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getProfileConfig', force))
|
||||
}
|
||||
|
||||
export async function setProfileConfig(config: IProfileConfig): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('setProfileConfig', config)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setProfileConfig', config))
|
||||
}
|
||||
|
||||
export async function getCurrentProfileItem(): Promise<IProfileItem> {
|
||||
return await window.electron.ipcRenderer.invoke('getCurrentProfileItem')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getCurrentProfileItem'))
|
||||
}
|
||||
|
||||
export async function getProfileItem(id: string | undefined): Promise<IProfileItem> {
|
||||
return await window.electron.ipcRenderer.invoke('getProfileItem', id)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getProfileItem', id))
|
||||
}
|
||||
|
||||
export async function changeCurrentProfile(id: string): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('changeCurrentProfile', id)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('changeCurrentProfile', id))
|
||||
}
|
||||
|
||||
export async function addProfileItem(item: Partial<IProfileItem>): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('addProfileItem', item)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('addProfileItem', item))
|
||||
}
|
||||
|
||||
export async function removeProfileItem(id: string): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('removeProfileItem', id)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('removeProfileItem', id))
|
||||
}
|
||||
|
||||
export async function updateProfileItem(item: IProfileItem): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('updateProfileItem', item)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateProfileItem', item))
|
||||
}
|
||||
|
||||
export async function getProfileStr(id: string): Promise<string> {
|
||||
return await window.electron.ipcRenderer.invoke('getProfileStr', id)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getProfileStr', id))
|
||||
}
|
||||
|
||||
export async function setProfileStr(id: string, str: string): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('setProfileStr', id, str)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setProfileStr', id, str))
|
||||
}
|
||||
|
||||
export async function getOverrideConfig(force = false): Promise<IOverrideConfig> {
|
||||
return await window.electron.ipcRenderer.invoke('getOverrideConfig', force)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverrideConfig', force))
|
||||
}
|
||||
|
||||
export async function setOverrideConfig(config: IOverrideConfig): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('setOverrideConfig', config)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setOverrideConfig', config))
|
||||
}
|
||||
|
||||
export async function getOverrideItem(id: string): Promise<IOverrideItem | undefined> {
|
||||
return await window.electron.ipcRenderer.invoke('getOverrideItem', id)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverrideItem', id))
|
||||
}
|
||||
|
||||
export async function addOverrideItem(item: Partial<IOverrideItem>): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('addOverrideItem', item)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('addOverrideItem', item))
|
||||
}
|
||||
|
||||
export async function removeOverrideItem(id: string): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('removeOverrideItem', id)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('removeOverrideItem', id))
|
||||
}
|
||||
|
||||
export async function updateOverrideItem(item: IOverrideItem): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('updateOverrideItem', item)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateOverrideItem', item))
|
||||
}
|
||||
|
||||
export async function getOverride(id: string): Promise<string> {
|
||||
return await window.electron.ipcRenderer.invoke('getOverride', id)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getOverride', id))
|
||||
}
|
||||
|
||||
export async function setOverride(id: string, str: string): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('setOverride', id, str)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setOverride', id, str))
|
||||
}
|
||||
|
||||
export async function restartCore(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('restartCore')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('restartCore'))
|
||||
}
|
||||
|
||||
export async function triggerSysProxy(enable: boolean): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('triggerSysProxy', enable)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('triggerSysProxy', enable))
|
||||
}
|
||||
|
||||
export async function isEncryptionAvailable(): Promise<boolean> {
|
||||
return await window.electron.ipcRenderer.invoke('isEncryptionAvailable')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('isEncryptionAvailable'))
|
||||
}
|
||||
|
||||
export async function encryptString(str: string): Promise<Buffer> {
|
||||
return await window.electron.ipcRenderer.invoke('encryptString', str)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('encryptString', str))
|
||||
}
|
||||
|
||||
export async function getFilePath(ext: string[]): Promise<string[] | undefined> {
|
||||
return await window.electron.ipcRenderer.invoke('getFilePath', ext)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getFilePath', ext))
|
||||
}
|
||||
|
||||
export async function readTextFile(filePath: string): Promise<string> {
|
||||
return await window.electron.ipcRenderer.invoke('readTextFile', filePath)
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('readTextFile', filePath))
|
||||
}
|
||||
|
||||
export async function getRuntimeConfigStr(): Promise<string> {
|
||||
return await window.electron.ipcRenderer.invoke('getRuntimeConfigStr')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getRuntimeConfigStr'))
|
||||
}
|
||||
|
||||
export async function getRuntimeConfig(): Promise<IMihomoConfig> {
|
||||
return await window.electron.ipcRenderer.invoke('getRuntimeConfig')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getRuntimeConfig'))
|
||||
}
|
||||
|
||||
export async function checkUpdate(): Promise<string | undefined> {
|
||||
return await window.electron.ipcRenderer.invoke('checkUpdate')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkUpdate'))
|
||||
}
|
||||
|
||||
export async function getVersion(): Promise<string> {
|
||||
return await window.electron.ipcRenderer.invoke('getVersion')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getVersion'))
|
||||
}
|
||||
|
||||
export async function getPlatform(): Promise<NodeJS.Platform> {
|
||||
return await window.electron.ipcRenderer.invoke('platform')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('platform'))
|
||||
}
|
||||
|
||||
export async function setupFirewall(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('setupFirewall')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('setupFirewall'))
|
||||
}
|
||||
|
||||
export async function quitApp(): Promise<void> {
|
||||
return await window.electron.ipcRenderer.invoke('quitApp')
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('quitApp'))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user