mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-02-10 11:40:28 +08:00
fix: resolve event listener memory leaks and add error logging
This commit is contained in:
parent
ae91194a74
commit
b60c01bb4c
@ -19,6 +19,8 @@ let logsRetry = 10
|
||||
let mihomoConnectionsWs: WebSocket | null = null
|
||||
let connectionsRetry = 10
|
||||
|
||||
const MAX_RETRY = 10
|
||||
|
||||
export const getAxios = async (force: boolean = false): Promise<AxiosInstance> => {
|
||||
const dynamicIpcPath = getMihomoIpcPath()
|
||||
|
||||
@ -233,6 +235,7 @@ export const mihomoSmartFlushCache = async (configName?: string): Promise<void>
|
||||
}
|
||||
|
||||
export const startMihomoTraffic = async (): Promise<void> => {
|
||||
trafficRetry = MAX_RETRY
|
||||
await mihomoTraffic()
|
||||
}
|
||||
|
||||
@ -258,7 +261,7 @@ const mihomoTraffic = async (): Promise<void> => {
|
||||
mihomoTrafficWs.onmessage = async (e): Promise<void> => {
|
||||
const data = e.data as string
|
||||
const json = JSON.parse(data) as IMihomoTrafficInfo
|
||||
trafficRetry = 10
|
||||
trafficRetry = MAX_RETRY
|
||||
try {
|
||||
mainWindow?.webContents.send('mihomoTraffic', json)
|
||||
if (process.platform !== 'linux') {
|
||||
@ -292,6 +295,7 @@ const mihomoTraffic = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
export const startMihomoMemory = async (): Promise<void> => {
|
||||
memoryRetry = MAX_RETRY
|
||||
await mihomoMemory()
|
||||
}
|
||||
|
||||
@ -314,7 +318,7 @@ const mihomoMemory = async (): Promise<void> => {
|
||||
|
||||
mihomoMemoryWs.onmessage = (e): void => {
|
||||
const data = e.data as string
|
||||
memoryRetry = 10
|
||||
memoryRetry = MAX_RETRY
|
||||
try {
|
||||
mainWindow?.webContents.send('mihomoMemory', JSON.parse(data) as IMihomoMemoryInfo)
|
||||
} catch {
|
||||
@ -338,6 +342,7 @@ const mihomoMemory = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
export const startMihomoLogs = async (): Promise<void> => {
|
||||
logsRetry = MAX_RETRY
|
||||
await mihomoLogs()
|
||||
}
|
||||
|
||||
@ -362,7 +367,7 @@ const mihomoLogs = async (): Promise<void> => {
|
||||
|
||||
mihomoLogsWs.onmessage = (e): void => {
|
||||
const data = e.data as string
|
||||
logsRetry = 10
|
||||
logsRetry = MAX_RETRY
|
||||
try {
|
||||
mainWindow?.webContents.send('mihomoLogs', JSON.parse(data) as IMihomoLogInfo)
|
||||
} catch {
|
||||
@ -386,6 +391,7 @@ const mihomoLogs = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
export const startMihomoConnections = async (): Promise<void> => {
|
||||
connectionsRetry = MAX_RETRY
|
||||
await mihomoConnections()
|
||||
}
|
||||
|
||||
@ -408,7 +414,7 @@ const mihomoConnections = async (): Promise<void> => {
|
||||
|
||||
mihomoConnectionsWs.onmessage = (e): void => {
|
||||
const data = e.data as string
|
||||
connectionsRetry = 10
|
||||
connectionsRetry = MAX_RETRY
|
||||
try {
|
||||
mainWindow?.webContents.send('mihomoConnections', JSON.parse(data) as IMihomoConnectionsInfo)
|
||||
} catch {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { addProfileItem, getCurrentProfileItem, getProfileConfig } from '../config'
|
||||
import { Cron } from 'croner'
|
||||
import { logger } from '../utils/logger'
|
||||
|
||||
const intervalPool: Record<string, Cron | NodeJS.Timeout> = {}
|
||||
|
||||
@ -15,8 +16,8 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
await logger.warn(`[ProfileUpdater] Failed to update profile ${item.name}:`, e)
|
||||
}
|
||||
},
|
||||
item.interval * 60 * 1000
|
||||
@ -26,16 +27,16 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
intervalPool[item.id] = new Cron(item.interval, async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
await logger.warn(`[ProfileUpdater] Failed to update profile ${item.name}:`, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
await logger.warn(`[ProfileUpdater] Failed to init profile ${item.name}:`, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,8 +47,8 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
await logger.warn(`[ProfileUpdater] Failed to update current profile:`, e)
|
||||
}
|
||||
},
|
||||
currentItem.interval * 60 * 1000
|
||||
@ -57,8 +58,8 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
await logger.warn(`[ProfileUpdater] Failed to update current profile:`, e)
|
||||
}
|
||||
},
|
||||
currentItem.interval * 60 * 1000 + 10000 // +10s
|
||||
@ -67,16 +68,16 @@ export async function initProfileUpdater(): Promise<void> {
|
||||
intervalPool[currentItem.id] = new Cron(currentItem.interval, async () => {
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
await logger.warn(`[ProfileUpdater] Failed to update current profile:`, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await addProfileItem(currentItem)
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
await logger.warn(`[ProfileUpdater] Failed to init current profile:`, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,8 +97,8 @@ export async function addProfileUpdater(item: IProfileItem): Promise<void> {
|
||||
async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
await logger.warn(`[ProfileUpdater] Failed to update profile ${item.name}:`, e)
|
||||
}
|
||||
},
|
||||
item.interval * 60 * 1000
|
||||
@ -106,8 +107,8 @@ export async function addProfileUpdater(item: IProfileItem): Promise<void> {
|
||||
intervalPool[item.id] = new Cron(item.interval, async () => {
|
||||
try {
|
||||
await addProfileItem(item)
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
await logger.warn(`[ProfileUpdater] Failed to update profile ${item.name}:`, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState, useCallback } from 'react'
|
||||
import MihomoIcon from './components/base/mihomo-icon'
|
||||
import { calcTraffic } from './utils/calc'
|
||||
import { showContextMenu, triggerMainWindow } from './utils/ipc'
|
||||
@ -48,17 +48,19 @@ const FloatingApp: React.FC = () => {
|
||||
}
|
||||
}, [spinSpeed, spinFloatingIcon])
|
||||
|
||||
useEffect(() => {
|
||||
window.electron.ipcRenderer.on('mihomoTraffic', async (_e, ...args) => {
|
||||
const info = args[0] as IMihomoTrafficInfo
|
||||
setUpload(info.up)
|
||||
setDownload(info.down)
|
||||
})
|
||||
return (): void => {
|
||||
window.electron.ipcRenderer.removeAllListeners('mihomoTraffic')
|
||||
}
|
||||
const handleTraffic = useCallback((_e: unknown, ...args: unknown[]) => {
|
||||
const info = args[0] as IMihomoTrafficInfo
|
||||
setUpload(info.up)
|
||||
setDownload(info.down)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
window.electron.ipcRenderer.on('mihomoTraffic', handleTraffic)
|
||||
return (): void => {
|
||||
window.electron.ipcRenderer.removeListener('mihomoTraffic', handleTraffic)
|
||||
}
|
||||
}, [handleTraffic])
|
||||
|
||||
return (
|
||||
<div className="app-drag h-screen w-screen overflow-hidden">
|
||||
<div className="floating-bg border border-divider flex bg-content1 h-full w-full">
|
||||
|
||||
@ -2,7 +2,7 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
|
||||
import { FaCircleArrowDown, FaCircleArrowUp } from 'react-icons/fa6'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { calcTraffic } from '@renderer/utils/calc'
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import React, { useEffect, useState, useMemo, useRef, useCallback } from 'react'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { IoLink } from 'react-icons/io5'
|
||||
@ -25,11 +25,6 @@ import { useTranslation } from 'react-i18next'
|
||||
// 注册 Chart.js 组件
|
||||
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Filler)
|
||||
|
||||
let currentUpload: number | undefined = undefined
|
||||
let currentDownload: number | undefined = undefined
|
||||
let hasShowTraffic = false
|
||||
let drawing = false
|
||||
|
||||
interface Props {
|
||||
iconOnly?: boolean
|
||||
}
|
||||
@ -61,6 +56,12 @@ const ConnCard: React.FC<Props> = (props) => {
|
||||
})
|
||||
const [series, setSeries] = useState(Array(10).fill(0))
|
||||
|
||||
// 使用 useRef 替代模块级变量
|
||||
const currentUploadRef = useRef<number | undefined>(undefined)
|
||||
const currentDownloadRef = useRef<number | undefined>(undefined)
|
||||
const hasShowTrafficRef = useRef(false)
|
||||
const drawingRef = useRef(false)
|
||||
|
||||
// Chart.js 配置
|
||||
const chartData = useMemo(() => {
|
||||
return {
|
||||
@ -125,36 +126,45 @@ const ConnCard: React.FC<Props> = (props) => {
|
||||
}
|
||||
|
||||
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
||||
useEffect(() => {
|
||||
window.electron.ipcRenderer.on('mihomoTraffic', async (_e, ...args) => {
|
||||
|
||||
// 使用 useCallback 创建稳定的 handler 引用
|
||||
const handleTraffic = useCallback(
|
||||
async (_e: unknown, ...args: unknown[]) => {
|
||||
const info = args[0] as IMihomoTrafficInfo
|
||||
setUpload(info.up)
|
||||
setDownload(info.down)
|
||||
const data = series
|
||||
data.shift()
|
||||
data.push(info.up + info.down)
|
||||
setSeries([...data])
|
||||
setSeries((prev) => {
|
||||
const data = [...prev]
|
||||
data.shift()
|
||||
data.push(info.up + info.down)
|
||||
return data
|
||||
})
|
||||
if (platform === 'darwin' && showTraffic) {
|
||||
if (drawing) return
|
||||
drawing = true
|
||||
if (drawingRef.current) return
|
||||
drawingRef.current = true
|
||||
try {
|
||||
await drawSvg(info.up, info.down)
|
||||
hasShowTraffic = true
|
||||
await drawSvg(info.up, info.down, currentUploadRef, currentDownloadRef)
|
||||
hasShowTrafficRef.current = true
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
drawing = false
|
||||
drawingRef.current = false
|
||||
}
|
||||
} else {
|
||||
if (!hasShowTraffic) return
|
||||
if (!hasShowTrafficRef.current) return
|
||||
window.electron.ipcRenderer.send('trayIconUpdate', trayIconBase64)
|
||||
hasShowTraffic = false
|
||||
hasShowTrafficRef.current = false
|
||||
}
|
||||
})
|
||||
},
|
||||
[showTraffic]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
window.electron.ipcRenderer.on('mihomoTraffic', handleTraffic)
|
||||
return (): void => {
|
||||
window.electron.ipcRenderer.removeAllListeners('mihomoTraffic')
|
||||
window.electron.ipcRenderer.removeListener('mihomoTraffic', handleTraffic)
|
||||
}
|
||||
}, [showTraffic])
|
||||
}, [handleTraffic])
|
||||
|
||||
if (iconOnly) {
|
||||
return (
|
||||
@ -274,10 +284,15 @@ const ConnCard: React.FC<Props> = (props) => {
|
||||
|
||||
export default ConnCard
|
||||
|
||||
const drawSvg = async (upload: number, download: number): Promise<void> => {
|
||||
if (upload === currentUpload && download === currentDownload) return
|
||||
currentUpload = upload
|
||||
currentDownload = download
|
||||
const drawSvg = async (
|
||||
upload: number,
|
||||
download: number,
|
||||
currentUploadRef: React.RefObject<number | undefined>,
|
||||
currentDownloadRef: React.RefObject<number | undefined>
|
||||
): Promise<void> => {
|
||||
if (upload === currentUploadRef.current && download === currentDownloadRef.current) return
|
||||
currentUploadRef.current = upload
|
||||
currentDownloadRef.current = download
|
||||
const svg = `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 36"><image height="36" width="36" href="${trayIconBase64}"/><text x="140" y="15" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end">${calcTraffic(upload)}/s</text><text x="140" y="34" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end">${calcTraffic(download)}/s</text></svg>`
|
||||
const image = await loadImage(svg)
|
||||
window.electron.ipcRenderer.send('trayIconUpdate', image)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user