mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +08:00
feat: support sub-store update & restart
This commit is contained in:
parent
8d88851444
commit
4da4c242ad
@ -2,12 +2,15 @@ import { getAppConfig, getControledMihomoConfig } from '../config'
|
|||||||
import { Worker } from 'worker_threads'
|
import { Worker } from 'worker_threads'
|
||||||
import { mihomoWorkDir, resourcesFilesDir, subStoreDir, substoreLogPath } from '../utils/dirs'
|
import { mihomoWorkDir, resourcesFilesDir, subStoreDir, substoreLogPath } from '../utils/dirs'
|
||||||
import subStoreIcon from '../../../resources/subStoreIcon.png?asset'
|
import subStoreIcon from '../../../resources/subStoreIcon.png?asset'
|
||||||
import { createWriteStream } from 'fs'
|
import { createWriteStream, existsSync, mkdirSync } from 'fs'
|
||||||
|
import { writeFile, rm, cp } from 'fs/promises'
|
||||||
import http from 'http'
|
import http from 'http'
|
||||||
import net from 'net'
|
import net from 'net'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { nativeImage } from 'electron'
|
import { nativeImage } from 'electron'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
import axios from 'axios'
|
||||||
|
import AdmZip from 'adm-zip'
|
||||||
|
|
||||||
export let pacPort: number
|
export let pacPort: number
|
||||||
export let subStorePort: number
|
export let subStorePort: number
|
||||||
@ -135,3 +138,66 @@ export async function stopSubStoreBackendServer(): Promise<void> {
|
|||||||
subStoreBackendWorker.terminate()
|
subStoreBackendWorker.terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function downloadSubStore(): Promise<void> {
|
||||||
|
const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
|
||||||
|
const frontendDir = path.join(resourcesFilesDir(), 'sub-store-frontend')
|
||||||
|
const backendPath = path.join(resourcesFilesDir(), 'sub-store.bundle.js')
|
||||||
|
const tempDir = path.join(resourcesFilesDir(), 'temp')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 下载后端文件
|
||||||
|
const backendRes = await axios.get(
|
||||||
|
'https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store.bundle.js',
|
||||||
|
{
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
headers: { 'Content-Type': 'application/octet-stream' },
|
||||||
|
proxy: {
|
||||||
|
protocol: 'http',
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: mixedPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await writeFile(backendPath, Buffer.from(backendRes.data))
|
||||||
|
|
||||||
|
// 下载前端文件
|
||||||
|
const frontendRes = await axios.get(
|
||||||
|
'https://github.com/sub-store-org/Sub-Store-Front-End/releases/latest/download/dist.zip',
|
||||||
|
{
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
headers: { 'Content-Type': 'application/octet-stream' },
|
||||||
|
proxy: {
|
||||||
|
protocol: 'http',
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: mixedPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 创建临时目录
|
||||||
|
if (existsSync(tempDir)) {
|
||||||
|
await rm(tempDir, { recursive: true })
|
||||||
|
}
|
||||||
|
mkdirSync(tempDir, { recursive: true })
|
||||||
|
|
||||||
|
// 先解压到临时目录
|
||||||
|
const zip = new AdmZip(Buffer.from(frontendRes.data))
|
||||||
|
zip.extractAllTo(tempDir, true)
|
||||||
|
|
||||||
|
// 确保目标目录存在并清空
|
||||||
|
if (existsSync(frontendDir)) {
|
||||||
|
await rm(frontendDir, { recursive: true })
|
||||||
|
}
|
||||||
|
mkdirSync(frontendDir, { recursive: true })
|
||||||
|
|
||||||
|
// 将 dist 目录中的内容移动到目标目录
|
||||||
|
await cp(path.join(tempDir, 'dist'), frontendDir, { recursive: true })
|
||||||
|
|
||||||
|
// 清理临时目录
|
||||||
|
await rm(tempDir, { recursive: true })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('substore.downloadFailed:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ import {
|
|||||||
startSubStoreBackendServer,
|
startSubStoreBackendServer,
|
||||||
stopSubStoreFrontendServer,
|
stopSubStoreFrontendServer,
|
||||||
stopSubStoreBackendServer,
|
stopSubStoreBackendServer,
|
||||||
|
downloadSubStore,
|
||||||
subStoreFrontendPort,
|
subStoreFrontendPort,
|
||||||
subStorePort
|
subStorePort
|
||||||
} from '../resolve/server'
|
} from '../resolve/server'
|
||||||
@ -202,6 +203,7 @@ export function registerIpcMainHandlers(): void {
|
|||||||
ipcMain.handle('stopSubStoreFrontendServer', () => ipcErrorWrapper(stopSubStoreFrontendServer)())
|
ipcMain.handle('stopSubStoreFrontendServer', () => ipcErrorWrapper(stopSubStoreFrontendServer)())
|
||||||
ipcMain.handle('startSubStoreBackendServer', () => ipcErrorWrapper(startSubStoreBackendServer)())
|
ipcMain.handle('startSubStoreBackendServer', () => ipcErrorWrapper(startSubStoreBackendServer)())
|
||||||
ipcMain.handle('stopSubStoreBackendServer', () => ipcErrorWrapper(stopSubStoreBackendServer)())
|
ipcMain.handle('stopSubStoreBackendServer', () => ipcErrorWrapper(stopSubStoreBackendServer)())
|
||||||
|
ipcMain.handle('downloadSubStore', () => ipcErrorWrapper(downloadSubStore)())
|
||||||
|
|
||||||
ipcMain.handle('subStorePort', () => subStorePort)
|
ipcMain.handle('subStorePort', () => subStorePort)
|
||||||
ipcMain.handle('subStoreFrontendPort', () => subStoreFrontendPort)
|
ipcMain.handle('subStoreFrontendPort', () => subStoreFrontendPort)
|
||||||
|
|||||||
@ -148,6 +148,11 @@
|
|||||||
"mihomo.ipSegment.placeholder": "IP Segment",
|
"mihomo.ipSegment.placeholder": "IP Segment",
|
||||||
"mihomo.interface.title": "Network Information",
|
"mihomo.interface.title": "Network Information",
|
||||||
"substore.title": "Sub-Store",
|
"substore.title": "Sub-Store",
|
||||||
|
"substore.checkUpdate": "Check Update",
|
||||||
|
"substore.updating": "Sub-Store is updating...",
|
||||||
|
"substore.updateCompleted": "Sub-Store update completed",
|
||||||
|
"substore.updateFailed": "Sub-Store update failed",
|
||||||
|
"substore.downloadFailed": "Failed to download Sub-Store file",
|
||||||
"substore.openInBrowser": "Open in Browser",
|
"substore.openInBrowser": "Open in Browser",
|
||||||
"substore.enable": "Enable Sub-Store",
|
"substore.enable": "Enable Sub-Store",
|
||||||
"substore.allowLan": "Allow LAN Access",
|
"substore.allowLan": "Allow LAN Access",
|
||||||
|
|||||||
@ -148,6 +148,11 @@
|
|||||||
"mihomo.ipSegment.placeholder": "IP 段",
|
"mihomo.ipSegment.placeholder": "IP 段",
|
||||||
"mihomo.interface.title": "网络信息",
|
"mihomo.interface.title": "网络信息",
|
||||||
"substore.title": "Sub-Store",
|
"substore.title": "Sub-Store",
|
||||||
|
"substore.checkUpdate": "检查更新",
|
||||||
|
"substore.updating": "Sub-Store 更新中...",
|
||||||
|
"substore.updateCompleted": "Sub-Store 更新完成",
|
||||||
|
"substore.updateFailed": "Sub-Store 更新失败",
|
||||||
|
"substore.downloadFailed": "下载 Sub-Store 文件失败",
|
||||||
"substore.openInBrowser": "在浏览器中打开",
|
"substore.openInBrowser": "在浏览器中打开",
|
||||||
"substore.enable": "启用 Sub-Store",
|
"substore.enable": "启用 Sub-Store",
|
||||||
"substore.allowLan": "允许局域网访问",
|
"substore.allowLan": "允许局域网访问",
|
||||||
|
|||||||
@ -1,10 +1,19 @@
|
|||||||
import { Button } from '@heroui/react'
|
import { Button } from '@heroui/react'
|
||||||
import BasePage from '@renderer/components/base/base-page'
|
import BasePage from '@renderer/components/base/base-page'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
import { subStoreFrontendPort, subStorePort } from '@renderer/utils/ipc'
|
import {
|
||||||
|
subStoreFrontendPort,
|
||||||
|
subStorePort,
|
||||||
|
startSubStoreFrontendServer,
|
||||||
|
startSubStoreBackendServer,
|
||||||
|
stopSubStoreFrontendServer,
|
||||||
|
stopSubStoreBackendServer,
|
||||||
|
downloadSubStore
|
||||||
|
} from '@renderer/utils/ipc'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { HiExternalLink } from 'react-icons/hi'
|
import { HiExternalLink } from 'react-icons/hi'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { IoMdCloudDownload } from 'react-icons/io'
|
||||||
|
|
||||||
const SubStore: React.FC = () => {
|
const SubStore: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -12,6 +21,7 @@ const SubStore: React.FC = () => {
|
|||||||
const { useCustomSubStore, customSubStoreUrl } = appConfig || {}
|
const { useCustomSubStore, customSubStoreUrl } = appConfig || {}
|
||||||
const [backendPort, setBackendPort] = useState<number | undefined>()
|
const [backendPort, setBackendPort] = useState<number | undefined>()
|
||||||
const [frontendPort, setFrontendPort] = useState<number | undefined>()
|
const [frontendPort, setFrontendPort] = useState<number | undefined>()
|
||||||
|
const [isUpdating, setIsUpdating] = useState(false)
|
||||||
const getPort = async (): Promise<void> => {
|
const getPort = async (): Promise<void> => {
|
||||||
setBackendPort(await subStorePort())
|
setBackendPort(await subStorePort())
|
||||||
setFrontendPort(await subStoreFrontendPort())
|
setFrontendPort(await subStoreFrontendPort())
|
||||||
@ -27,6 +37,36 @@ const SubStore: React.FC = () => {
|
|||||||
<BasePage
|
<BasePage
|
||||||
title={t('substore.title')}
|
title={t('substore.title')}
|
||||||
header={
|
header={
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
title={t('substore.checkUpdate')}
|
||||||
|
isIconOnly
|
||||||
|
size="sm"
|
||||||
|
className="app-nodrag"
|
||||||
|
variant="light"
|
||||||
|
isLoading={isUpdating}
|
||||||
|
onPress={async () => {
|
||||||
|
try {
|
||||||
|
new Notification(t('substore.updating'))
|
||||||
|
setIsUpdating(true)
|
||||||
|
await downloadSubStore()
|
||||||
|
await stopSubStoreBackendServer()
|
||||||
|
await startSubStoreBackendServer()
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
|
setFrontendPort(0)
|
||||||
|
await stopSubStoreFrontendServer()
|
||||||
|
await startSubStoreFrontendServer()
|
||||||
|
await getPort()
|
||||||
|
new Notification(t('substore.updateCompleted'))
|
||||||
|
} catch (e) {
|
||||||
|
new Notification(`${t('substore.updateFailed')}: ${e}`)
|
||||||
|
} finally {
|
||||||
|
setIsUpdating(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IoMdCloudDownload className="text-lg" />
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
title={t('substore.openInBrowser')}
|
title={t('substore.openInBrowser')}
|
||||||
isIconOnly
|
isIconOnly
|
||||||
@ -41,6 +81,7 @@ const SubStore: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<HiExternalLink className="text-lg" />
|
<HiExternalLink className="text-lg" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<iframe
|
<iframe
|
||||||
|
|||||||
@ -326,6 +326,9 @@ export async function startSubStoreBackendServer(): Promise<void> {
|
|||||||
export async function stopSubStoreBackendServer(): Promise<void> {
|
export async function stopSubStoreBackendServer(): Promise<void> {
|
||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('stopSubStoreBackendServer'))
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('stopSubStoreBackendServer'))
|
||||||
}
|
}
|
||||||
|
export async function downloadSubStore(): Promise<void> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('downloadSubStore'))
|
||||||
|
}
|
||||||
|
|
||||||
export async function subStorePort(): Promise<number> {
|
export async function subStorePort(): Promise<number> {
|
||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStorePort'))
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStorePort'))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user