mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 02:52:07 +08:00
fix(proxy): unify node switching and connection cleanup
Route frontend and tray node selections through the same backend flow for selection, persistence, state refresh, and connection cleanup. Read the group's previous selection before switching and preserve the original rule of closing only connections whose chains contain that selection. Run optional cleanup in the background and keep its failures separate from successful selection. Remove redundant frontend effects and unused IPC commands, and refresh the proxy view for every successful switch. Refs:32095daf90Refs:bea0dde074
This commit is contained in:
@@ -5,16 +5,10 @@ use crate::{
|
||||
core::{
|
||||
handle::Handle,
|
||||
proxy_view::{GroupScope, ProxyViewBuilder, ProxyViewInput, ProxyViewV1},
|
||||
tray::Tray,
|
||||
},
|
||||
process::AsyncHandler,
|
||||
};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use serde_yaml_ng::Mapping;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
/// Merges one selection pair into fresh backend state to avoid stale-list overwrites.
|
||||
#[tauri::command]
|
||||
@@ -27,15 +21,12 @@ pub async fn record_selected_node(group_name: String, node: String) -> CmdResult
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn forget_selected_node(group_name: String) -> CmdResult<()> {
|
||||
crate::config::profiles::forget_selected_node(&group_name)
|
||||
pub async fn switch_proxy_node(group_name: String, proxy_name: String, unfix: bool) -> CmdResult<()> {
|
||||
crate::feat::switch_proxy_node(&group_name, &proxy_name, unfix)
|
||||
.await
|
||||
.stringify_err()
|
||||
}
|
||||
|
||||
static TRAY_SYNC_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
static TRAY_SYNC_PENDING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn runtime_group_order(config: Option<&Mapping>) -> Vec<String> {
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
@@ -124,48 +115,3 @@ pub async fn get_proxy_view() -> CmdResult<ProxyViewV1> {
|
||||
providers: providers.ok(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn sync_tray_proxy_selection() -> CmdResult<()> {
|
||||
if TRAY_SYNC_RUNNING
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_ok()
|
||||
{
|
||||
AsyncHandler::spawn(move || async move {
|
||||
run_tray_sync_loop().await;
|
||||
});
|
||||
} else {
|
||||
TRAY_SYNC_PENDING.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_tray_sync_loop() {
|
||||
// Permanent watcher: keeps syncing until a pass finds no pending sync request.
|
||||
loop {
|
||||
match Tray::global().update_menu().await {
|
||||
Ok(_) => {
|
||||
logging!(debug, Type::Cmd, "Tray proxy selection synced successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(error, Type::Cmd, "Failed to sync tray proxy selection: {e:#}");
|
||||
}
|
||||
}
|
||||
|
||||
if !TRAY_SYNC_PENDING.swap(false, Ordering::AcqRel) {
|
||||
TRAY_SYNC_RUNNING.store(false, Ordering::Release);
|
||||
|
||||
if TRAY_SYNC_PENDING.swap(false, Ordering::AcqRel)
|
||||
&& TRAY_SYNC_RUNNING
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_ok()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1000,7 +1000,9 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
|
||||
Some((g, p)) => (g, p),
|
||||
None => return,
|
||||
};
|
||||
feat::switch_proxy_node(group_name, proxy_name).await;
|
||||
if let Err(err) = feat::switch_proxy_node(group_name, proxy_name, false).await {
|
||||
logging!(error, Type::Tray, "Failed to switch proxy: {err:#}");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
logging!(debug, Type::Tray, "Unhandled tray menu event: {:?}", event.id);
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
cmd,
|
||||
config::{Config, PrfItem, PrfOption, profiles::profiles_update_item_safe},
|
||||
core::{CoreManager, handle, tray, validate::ValidationOutcome},
|
||||
process::AsyncHandler,
|
||||
utils::help::{mask_err, mask_url},
|
||||
};
|
||||
use anyhow::{Result, bail};
|
||||
@@ -21,37 +22,85 @@ pub async fn toggle_proxy_profile(profile_index: String) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tell the profile which node this group is on now.
|
||||
///
|
||||
/// The core is already switched by the time this runs, so failing to record is not a reason to
|
||||
/// report the switch as failed — but it is worth a log line, because what the profile holds is
|
||||
/// what gets re-applied the next time a core starts.
|
||||
async fn record_switched_node(group_name: &str, proxy_name: &str) {
|
||||
if let Err(error) = crate::config::profiles::record_selected_node(group_name, proxy_name).await {
|
||||
pub async fn switch_proxy_node(group_name: &str, proxy_name: &str, unfix: bool) -> Result<()> {
|
||||
let mihomo = handle::Handle::mihomo();
|
||||
let previous_proxy = if Config::verge().await.data_arc().auto_close_connection.unwrap_or(false) {
|
||||
match mihomo.get_proxy_by_name(group_name).await {
|
||||
Ok(group) => group.now.filter(|name| !name.is_empty()),
|
||||
Err(err) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to read previous proxy selection for {group_name}: {err}"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if unfix {
|
||||
mihomo.unfixed_proxy(group_name).await?;
|
||||
} else {
|
||||
mihomo.select_node_for_group(group_name, proxy_name).await?;
|
||||
}
|
||||
|
||||
let save_result = if unfix {
|
||||
crate::config::profiles::forget_selected_node(group_name).await
|
||||
} else {
|
||||
crate::config::profiles::record_selected_node(group_name, proxy_name).await
|
||||
};
|
||||
// The core selection is already applied; persistence failure must not report it as failed.
|
||||
if let Err(error) = save_result {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
"切换代理成功但未能记录到配置: {} -> {}, 错误: {error:#}",
|
||||
group_name,
|
||||
proxy_name
|
||||
"Failed to persist proxy selection for {group_name}: {error:#}"
|
||||
);
|
||||
}
|
||||
}
|
||||
announce(Refresh::Proxies);
|
||||
|
||||
pub async fn switch_proxy_node(group_name: &str, proxy_name: &str) {
|
||||
match handle::Handle::mihomo()
|
||||
.select_node_for_group(group_name, proxy_name)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
record_switched_node(group_name, proxy_name).await;
|
||||
announce(Refresh::Proxies);
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Tray, "切换代理失败: {err:?}");
|
||||
}
|
||||
if let Some(previous_proxy) = previous_proxy {
|
||||
AsyncHandler::spawn(move || async move {
|
||||
let connections = match mihomo.get_connections().await {
|
||||
Ok(connections) => connections,
|
||||
Err(err) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to read connections after switching proxy: {err}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
futures::future::join_all(
|
||||
connections
|
||||
.connections
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter(|connection| connection.chains.contains(&previous_proxy))
|
||||
.map(|connection| async move {
|
||||
if let Err(err) = mihomo.close_connection(&connection.id).await {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to close connection {} after switching proxy: {err}",
|
||||
connection.id
|
||||
);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
if let Err(err) = tray::Tray::global().update_menu().await {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Tray,
|
||||
"Failed to refresh tray after switching proxy: {err:#}"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn should_update_profile(uid: &String, ignore_auto_update: bool) -> Result<Option<(String, Option<PrfOption>)>> {
|
||||
|
||||
@@ -45,9 +45,8 @@ pub fn builder() -> tauri_specta::Builder<tauri::Wry> {
|
||||
cmd::update_proxy_chain_config_in_runtime,
|
||||
cmd::invoke_uwp_tool,
|
||||
cmd::copy_clash_env,
|
||||
cmd::sync_tray_proxy_selection,
|
||||
cmd::record_selected_node,
|
||||
cmd::forget_selected_node,
|
||||
cmd::switch_proxy_node,
|
||||
cmd::save_dns_config,
|
||||
cmd::apply_dns_config,
|
||||
cmd::set_dns_override,
|
||||
|
||||
@@ -606,9 +606,6 @@ export const CurrentProxyCard = () => {
|
||||
)
|
||||
|
||||
const { select } = useProxySelection({
|
||||
onSuccess: () => {
|
||||
refreshProxy()
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('代理切换失败', error)
|
||||
refreshProxy()
|
||||
@@ -755,10 +752,9 @@ export const CurrentProxyCard = () => {
|
||||
if (!selectedGroup || !option || !isInteractableMember(option.member)) {
|
||||
return
|
||||
}
|
||||
const previousProxy = selectedGroup.now
|
||||
const nextName = option.member.ref.name
|
||||
// The profile selection is the durable source across core restarts and run modes.
|
||||
select(selectedGroup.name, nextName, previousProxy!, selectedGroup.fixed!)
|
||||
select(selectedGroup.name, nextName, selectedGroup.fixed!)
|
||||
},
|
||||
[isDirectMode, select, selectedGroup, unsortedProxyOptions],
|
||||
)
|
||||
|
||||
@@ -470,9 +470,6 @@ function NormalProxyGroups(props: { mode: string }) {
|
||||
}, [handleScroll])
|
||||
|
||||
const { select } = useProxySelection({
|
||||
onSuccess: () => {
|
||||
onProxies()
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('代理切换失败', error)
|
||||
onProxies()
|
||||
@@ -487,12 +484,12 @@ function NormalProxyGroups(props: { mode: string }) {
|
||||
if (group.name === globalGroupName) {
|
||||
pendingRevealRef.current =
|
||||
member.kind === 'group' ? member.ref.name : null
|
||||
// The selection refetches the view, so skip onHeadState's refetch.
|
||||
// The backend selection event refreshes the view.
|
||||
if (member.kind === 'group') {
|
||||
setHeadState(member.ref.name, { open: true })
|
||||
}
|
||||
}
|
||||
select(group.name, member.ref.name, group.now!, group.fixed!)
|
||||
select(group.name, member.ref.name, group.fixed!)
|
||||
},
|
||||
[globalGroupName, select, setHeadState],
|
||||
)
|
||||
|
||||
@@ -1,51 +1,35 @@
|
||||
import { selectNodeForGroup } from 'tauri-plugin-mihomo-api'
|
||||
import { expect, it, vi } from 'vitest'
|
||||
|
||||
import { switchProxyNode } from '@/services/cmds'
|
||||
|
||||
import { useProxySelection } from './use-proxy-selection'
|
||||
|
||||
const record = vi.hoisted(() => vi.fn())
|
||||
vi.mock('react', () => ({
|
||||
useCallback: (callback: unknown) => callback,
|
||||
useRef: (current: unknown) => ({ current }),
|
||||
}))
|
||||
vi.mock('@/hooks/use-record-selection', () => ({
|
||||
useRecordSelection: () => record,
|
||||
useForgetSelection: () => vi.fn(),
|
||||
}))
|
||||
vi.mock('@/hooks/use-verge', () => ({ useVerge: () => ({ verge: {} }) }))
|
||||
vi.mock('tauri-plugin-mihomo-api', () => ({
|
||||
selectNodeForGroup: vi.fn(),
|
||||
unfixedProxy: vi.fn(),
|
||||
}))
|
||||
vi.mock('@/services/cmds', () => ({ syncTrayProxySelection: vi.fn() }))
|
||||
vi.mock('@/services/cmds', () => ({ switchProxyNode: vi.fn() }))
|
||||
vi.mock('@/services/notice-service', () => ({ showNotice: { error: vi.fn() } }))
|
||||
vi.mock('@/services/query-client', () => ({ revalidateQueries: vi.fn() }))
|
||||
|
||||
it('applies and records independent group selections while another group is pending', async () => {
|
||||
it('requests independent group selections while another group is pending', async () => {
|
||||
let finishFirst!: () => void
|
||||
vi.mocked(selectNodeForGroup)
|
||||
vi.mocked(switchProxyNode)
|
||||
.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
finishFirst = resolve
|
||||
finishFirst = () => resolve(null)
|
||||
}),
|
||||
)
|
||||
.mockResolvedValueOnce(undefined)
|
||||
const firstSuccess = vi.fn()
|
||||
const secondSuccess = vi.fn()
|
||||
const first = useProxySelection({ onSuccess: firstSuccess })
|
||||
const second = useProxySelection({ onSuccess: secondSuccess })
|
||||
.mockResolvedValueOnce(null)
|
||||
const first = useProxySelection()
|
||||
const second = useProxySelection()
|
||||
first.select('Group A', 'Node A')
|
||||
second.select('Group B', 'Node B')
|
||||
second.select('Group B', 'Node B', 'Node B')
|
||||
await vi.waitFor(() =>
|
||||
expect(record).toHaveBeenCalledWith('Group B', 'Node B'),
|
||||
expect(switchProxyNode).toHaveBeenCalledWith('Group B', 'Node B', true),
|
||||
)
|
||||
expect(selectNodeForGroup).toHaveBeenCalledTimes(2)
|
||||
expect(firstSuccess).not.toHaveBeenCalled()
|
||||
expect(secondSuccess).toHaveBeenCalledOnce()
|
||||
expect(switchProxyNode).toHaveBeenCalledWith('Group A', 'Node A', false)
|
||||
expect(switchProxyNode).toHaveBeenCalledTimes(2)
|
||||
finishFirst()
|
||||
await vi.waitFor(() =>
|
||||
expect(record).toHaveBeenCalledWith('Group A', 'Node A'),
|
||||
)
|
||||
expect(firstSuccess).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
@@ -1,121 +1,39 @@
|
||||
import { useCallback, useRef } from 'react'
|
||||
import {
|
||||
closeConnection,
|
||||
getConnections,
|
||||
selectNodeForGroup,
|
||||
unfixedProxy,
|
||||
} from 'tauri-plugin-mihomo-api'
|
||||
|
||||
import {
|
||||
useForgetSelection,
|
||||
useRecordSelection,
|
||||
} from '@/hooks/use-record-selection'
|
||||
import { useVerge } from '@/hooks/use-verge'
|
||||
import { syncTrayProxySelection } from '@/services/cmds'
|
||||
import { switchProxyNode } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
import { debugLog } from '@/utils/debug'
|
||||
|
||||
// 缓存连接清理
|
||||
const cleanupConnections = async (previousProxy: string) => {
|
||||
try {
|
||||
const { connections } = await getConnections()
|
||||
const cleanupPromises = (connections ?? [])
|
||||
.filter((conn) => conn.chains.includes(previousProxy))
|
||||
.map((conn) => closeConnection(conn.id))
|
||||
|
||||
if (cleanupPromises.length > 0) {
|
||||
await Promise.allSettled(cleanupPromises)
|
||||
debugLog(`[ProxySelection] 清理了 ${cleanupPromises.length} 个连接`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[ProxySelection] 连接清理失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
interface ProxySelectionOptions {
|
||||
onSuccess?: () => void
|
||||
onError?: (error: any) => void
|
||||
}
|
||||
|
||||
interface ProxyChangeRequest {
|
||||
groupName: string
|
||||
proxyName: string
|
||||
previousProxy?: string
|
||||
fixed?: string
|
||||
}
|
||||
|
||||
// 代理选择 Hook
|
||||
export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||
const recordSelection = useRecordSelection()
|
||||
const forgetSelection = useForgetSelection()
|
||||
const { verge } = useVerge()
|
||||
const pendingRequestRef = useRef<ProxyChangeRequest | null>(null)
|
||||
const isProcessingRef = useRef(false)
|
||||
|
||||
const { onSuccess, onError } = options
|
||||
|
||||
const autoCloseConnection = verge?.auto_close_connection ?? false
|
||||
|
||||
// 切换节点
|
||||
const syncTraySelection = useCallback(() => {
|
||||
mutate(() => syncTrayProxySelection(), {
|
||||
id: 'sync-tray-selection',
|
||||
errorNotice: false,
|
||||
}).catch((error) => {
|
||||
console.error('[ProxySelection] 托盘状态同步失败:', error)
|
||||
})
|
||||
}, [])
|
||||
const { onError } = options
|
||||
|
||||
const executeChange = useCallback(
|
||||
async (request: ProxyChangeRequest) => {
|
||||
const { groupName, proxyName, previousProxy, fixed } = request
|
||||
const isFixedProxy = fixed === proxyName
|
||||
if (isFixedProxy) {
|
||||
debugLog(`[ProxySelection] 代理取消固定: ${groupName} -> ${proxyName}`)
|
||||
} else {
|
||||
debugLog(`[ProxySelection] 代理切换: ${groupName} -> ${proxyName}`)
|
||||
}
|
||||
|
||||
async ({ groupName, proxyName, fixed }: ProxyChangeRequest) => {
|
||||
try {
|
||||
const result = await mutate(
|
||||
() =>
|
||||
isFixedProxy
|
||||
? unfixedProxy(groupName)
|
||||
: selectNodeForGroup(groupName, proxyName),
|
||||
await mutate(
|
||||
() => switchProxyNode(groupName, proxyName, fixed === proxyName),
|
||||
{ id: `select-proxy-node:${groupName}`, errorNotice: false },
|
||||
)
|
||||
if (!result.ok) return
|
||||
onSuccess?.()
|
||||
syncTraySelection()
|
||||
if (isFixedProxy) {
|
||||
await forgetSelection(groupName)
|
||||
debugLog(`[ProxySelection] 代理和状态同步完成: ${groupName}`)
|
||||
} else {
|
||||
await recordSelection(groupName, proxyName)
|
||||
debugLog(
|
||||
`[ProxySelection] 代理和状态同步完成: ${groupName} -> ${proxyName}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (autoCloseConnection && previousProxy) {
|
||||
void cleanupConnections(previousProxy)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ProxySelection] 代理切换失败: ${groupName} -> ${proxyName}`,
|
||||
`[ProxySelection] Failed to switch proxy: ${groupName} -> ${proxyName}`,
|
||||
error,
|
||||
)
|
||||
onError?.(error)
|
||||
}
|
||||
},
|
||||
[
|
||||
autoCloseConnection,
|
||||
forgetSelection,
|
||||
onError,
|
||||
onSuccess,
|
||||
recordSelection,
|
||||
syncTraySelection,
|
||||
],
|
||||
[onError],
|
||||
)
|
||||
|
||||
const flushChangeQueue = useCallback(async () => {
|
||||
@@ -137,16 +55,10 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||
}, [executeChange])
|
||||
|
||||
const select = useCallback(
|
||||
(
|
||||
groupName: string,
|
||||
proxyName: string,
|
||||
previousProxy?: string,
|
||||
fixed?: string,
|
||||
) => {
|
||||
(groupName: string, proxyName: string, fixed?: string) => {
|
||||
pendingRequestRef.current = {
|
||||
groupName,
|
||||
proxyName,
|
||||
previousProxy,
|
||||
fixed,
|
||||
}
|
||||
void flushChangeQueue()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { forgetSelectedNode, recordSelectedNode } from '@/services/cmds'
|
||||
import { recordSelectedNode } from '@/services/cmds'
|
||||
import { mutate } from '@/services/mutate'
|
||||
|
||||
/**
|
||||
@@ -19,16 +19,3 @@ export const useRecordSelection = () => {
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
export const useForgetSelection = () => {
|
||||
return useCallback(async (groupName: string) => {
|
||||
try {
|
||||
await mutate(() => forgetSelectedNode(groupName), {
|
||||
id: `persist-selected-node:${groupName}`,
|
||||
errorNotice: false,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[Selection] 清除代理选择失败:', error)
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -78,13 +78,11 @@ export const commands = {
|
||||
}),
|
||||
invokeUwpTool: () => __TAURI_INVOKE<null>('invoke_uwp_tool'),
|
||||
copyClashEnv: () => __TAURI_INVOKE<null>('copy_clash_env'),
|
||||
syncTrayProxySelection: () =>
|
||||
__TAURI_INVOKE<null>('sync_tray_proxy_selection'),
|
||||
/** Merges one selection pair into fresh backend state to avoid stale-list overwrites. */
|
||||
recordSelectedNode: (groupName: string, node: string) =>
|
||||
__TAURI_INVOKE<null>('record_selected_node', { groupName, node }),
|
||||
forgetSelectedNode: (groupName: string) =>
|
||||
__TAURI_INVOKE<null>('forget_selected_node', { groupName }),
|
||||
switchProxyNode: (groupName: string, proxyName: string, unfix: boolean) =>
|
||||
__TAURI_INVOKE<null>('switch_proxy_node', { groupName, proxyName, unfix }),
|
||||
saveDnsConfig: (dnsConfig: YamlMapping) =>
|
||||
__TAURI_INVOKE<null>('save_dns_config', { dnsConfig }),
|
||||
applyDnsConfig: (apply: boolean) =>
|
||||
|
||||
@@ -29,14 +29,11 @@ export interface BusHandlerDeps {
|
||||
/**
|
||||
* Pure per-event handler table for the bus. Handlers may trigger documented
|
||||
* side effects (SWR revalidation, notice toasts) but write store state only
|
||||
* through dispatched pure actions. Created exactly once per bus mount; the
|
||||
* throttles below live in the returned closure, mirroring the previous
|
||||
* scattered listeners.
|
||||
* through dispatched pure actions. Created exactly once per bus mount.
|
||||
*/
|
||||
export const createEventBus = (deps: BusHandlerDeps) => {
|
||||
let lastProfileId: string | null = null
|
||||
let lastProfileChangeTime = 0
|
||||
let lastProxyRefreshTime = 0
|
||||
const refreshThrottle = 800
|
||||
|
||||
const handleProfileChanged = (newProfileId: string) => {
|
||||
@@ -52,13 +49,6 @@ export const createEventBus = (deps: BusHandlerDeps) => {
|
||||
deps.revalidateKeys(['getProfiles'])
|
||||
}
|
||||
|
||||
const handleRefreshProxyConfig = () => {
|
||||
const now = Date.now()
|
||||
if (now - lastProxyRefreshTime <= refreshThrottle) return
|
||||
lastProxyRefreshTime = now
|
||||
deps.revalidateKeys(['getProxyView'])
|
||||
}
|
||||
|
||||
const handlers: BusHandlers = {
|
||||
'verge://refresh-clash-config': () =>
|
||||
deps.revalidateKeys([
|
||||
@@ -74,7 +64,7 @@ export const createEventBus = (deps: BusHandlerDeps) => {
|
||||
'verge://refresh-verge-config': () =>
|
||||
deps.revalidateKeys(['getVergeConfig', 'getSystemProxy', 'getAutoProxy']),
|
||||
'verge://refresh-profiles': () => deps.revalidateKeys(['getProfiles']),
|
||||
'verge://refresh-proxy-config': handleRefreshProxyConfig,
|
||||
'verge://refresh-proxy-config': () => deps.revalidateKeys(['getProxyView']),
|
||||
'verge://notice-message': (payload) => deps.handleNotice(payload),
|
||||
'profile-changed': handleProfileChanged,
|
||||
'verge://timer-updated': (uid) =>
|
||||
|
||||
@@ -152,17 +152,17 @@ export async function patchClashMode(payload: string) {
|
||||
return commands.patchClashMode(payload)
|
||||
}
|
||||
|
||||
export async function syncTrayProxySelection() {
|
||||
return commands.syncTrayProxySelection()
|
||||
}
|
||||
|
||||
/** Sends one selection pair so the backend can merge against current profile state. */
|
||||
export async function recordSelectedNode(groupName: string, node: string) {
|
||||
return commands.recordSelectedNode(groupName, node)
|
||||
}
|
||||
|
||||
export async function forgetSelectedNode(groupName: string) {
|
||||
return commands.forgetSelectedNode(groupName)
|
||||
export async function switchProxyNode(
|
||||
groupName: string,
|
||||
proxyName: string,
|
||||
unfix: boolean,
|
||||
) {
|
||||
return commands.switchProxyNode(groupName, proxyName, unfix)
|
||||
}
|
||||
|
||||
export async function getProxyView(): Promise<ProxyViewV1> {
|
||||
|
||||
Reference in New Issue
Block a user