diff --git a/Changelog.md b/Changelog.md
index 3a7a59e8e..d6cbba0af 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -13,6 +13,8 @@
- 修复 Fake-IP 模式开启 IPv6 后未生成 fake-ip-range6
- 修复 DNS 覆写的高级模式无法正常编辑
- 修复部分非标准 WebDAV 服务器在备份目录已存在时的问题
+- 修复 Linux 应用内更新问题
+- 修复 JS 脚本验证因 console 方法调用导致的执行失败问题
✨ 新增功能
@@ -29,5 +31,6 @@
- 更健壮的 service 生命周期管理
- 更健壮的 Mihomo API 通信机制
- 关闭 autofill 弹出窗口
+- 改进切换订阅后激活选中节点的逻辑
diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs
index 8eb631dc7..4c926d2a7 100644
--- a/src-tauri/src/cmd/profile.rs
+++ b/src-tauri/src/cmd/profile.rs
@@ -1,6 +1,7 @@
use super::CmdResult;
use super::StringifyErr as _;
use crate::cmd::validate::{ValidationNoticeTarget, handle_validation_notice};
+use crate::config::profiles;
use crate::utils::window_manager::WindowManager;
use crate::{
config::{
@@ -212,6 +213,7 @@ async fn restore_previous_profile(prev_profile: &String) -> CmdResult<()> {
async fn handle_success(current_value: Option<&String>) -> CmdResult {
Config::profiles().await.apply();
+ profiles::activate_selected_nodes().await.stringify_err()?;
handle::Handle::refresh_clash();
if let Err(e) = Tray::global().update_tooltip().await {
diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs
index 7d5401c00..0c6dfaff3 100644
--- a/src-tauri/src/config/profiles.rs
+++ b/src-tauri/src/config/profiles.rs
@@ -1,15 +1,19 @@
use super::{PrfOption, prfitem::PrfItem};
-use crate::utils::{
- dirs::{self, PathBufExec as _},
- help,
+use crate::{
+ core::handle,
+ utils::{
+ dirs::{self, PathBufExec as _},
+ help,
+ },
};
use anyhow::{Context as _, Result, bail};
use clash_verge_logging::{Type, logging};
+use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use serde_yaml_ng::Mapping;
use smartstring::alias::String;
-use std::{collections::HashSet, sync::LazyLock};
-use tokio::fs;
+use std::{collections::HashSet, sync::LazyLock, time::Duration};
+use tokio::{fs, task::JoinHandle};
/// Regex to check profile file names, eg.
/// R12345678.yaml (remote)
@@ -23,6 +27,9 @@ use tokio::fs;
static REGEX_PROFILE_FILE: LazyLock =
LazyLock::new(|| regex::Regex::new(r"^(?:[RLmrpg][a-zA-Z0-9]+\.yaml|s[a-zA-Z0-9]+\.js)$").unwrap());
+// activate selected nodes task handle
+static ACTIVATE_SELECTED_TASK: LazyLock>>> = LazyLock::new(|| Mutex::new(None));
+
/// Define the `profiles.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IProfiles {
@@ -584,3 +591,90 @@ pub async fn profiles_draft_update_item_safe(index: &String, item: &mut PrfItem)
})
.await
}
+
+pub async fn activate_selected_nodes() -> Result<()> {
+ logging!(info, Type::Config, "starting activating selected nodes");
+ let value = ACTIVATE_SELECTED_TASK.lock().take();
+ if let Some(handle) = value {
+ logging!(info, Type::Config, "aborting previous worker");
+ handle.abort();
+ }
+ let profiles = Config::profiles().await.latest_arc();
+ let Some(current) = profiles.get_current() else {
+ bail!("no current profile running");
+ };
+ let profile = profiles
+ .get_item(current)
+ .context("failed to get current profile")?
+ .clone();
+
+ let handle = tokio::spawn(async move {
+ let mihomo = handle::Handle::mihomo().await;
+ // check mihomo is running
+ let mut is_mihomo_ready = false;
+ for _ in 0..10 {
+ if mihomo.get_version().await.is_ok() {
+ logging!(debug, Type::Config, "check mihomo api success");
+ is_mihomo_ready = true;
+ break;
+ }
+ tokio::time::sleep(Duration::from_secs(1)).await;
+ }
+
+ if !is_mihomo_ready {
+ logging!(
+ error,
+ Type::Config,
+ "check that the mihomo api reaches the maximum number of retries, maybe mihomo core is not running"
+ );
+ return;
+ }
+
+ if let Some(selected) = profile.selected.as_ref() {
+ logging!(debug, Type::Config, "selected nodes: {selected:?}");
+ for selected_item in selected {
+ let mut retry = 10;
+ if let Some(group_name) = selected_item.name.as_ref()
+ && let Some(node) = selected_item.now.as_ref()
+ {
+ while retry >= 0 {
+ logging!(debug, Type::Config, "check node[{node}] exists");
+ if mihomo.get_proxy_by_name(node).await.is_ok() {
+ logging!(debug, Type::Config, "node[{node}] exists");
+ break;
+ }
+ retry -= 1;
+ tokio::time::sleep(Duration::from_secs(1)).await;
+ }
+ if retry < 0 {
+ logging!(
+ error,
+ Type::Config,
+ "Failed to select node for proxy: {group_name}, node: {node}, because the node [{node}] does not exist"
+ );
+ continue;
+ }
+ if mihomo.select_node_for_group(group_name, node).await.is_err() {
+ logging!(
+ error,
+ Type::Config,
+ "Failed to select node for proxy: {group_name}, node: {node}"
+ );
+ } else {
+ logging!(
+ info,
+ Type::Config,
+ "Selected node for proxy: {group_name}, node: {node}"
+ );
+ }
+ }
+ }
+ // refresh clash
+ handle::Handle::refresh_clash();
+ }
+ logging!(info, Type::Config, "activating selected nodes done!");
+ *ACTIVATE_SELECTED_TASK.lock() = None;
+ });
+ *ACTIVATE_SELECTED_TASK.lock() = Some(handle);
+ Ok(())
+}
diff --git a/src/hooks/use-profiles.ts b/src/hooks/use-profiles.ts
index f35644552..15be12b13 100644
--- a/src/hooks/use-profiles.ts
+++ b/src/hooks/use-profiles.ts
@@ -1,12 +1,5 @@
-import { selectNodeForGroup } from 'tauri-plugin-mihomo-api'
-
-import {
- calcuProxies,
- getProfiles,
- patchProfile,
- patchProfilesConfig,
-} from '@/services/cmds'
-import { setCacheData, useQuery } from '@/services/query-client'
+import { getProfiles, patchProfile, patchProfilesConfig } from '@/services/cmds'
+import { useQuery } from '@/services/query-client'
import { debugLog } from '@/utils/debug'
export const useProfiles = () => {
@@ -70,155 +63,13 @@ export const useProfiles = () => {
const patchCurrent = async (value: Partial) => {
if (profiles?.current) {
await patchProfile(profiles.current, value)
- if (!value.selected) {
- mutateProfiles()
- }
- }
- }
-
- // 根据selected的节点选择
- const activateSelected = async (profileOverride?: IProfilesConfig) => {
- try {
- debugLog('[ActivateSelected] 开始处理代理选择')
-
- const proxiesData = await calcuProxies()
- const profileData = profileOverride ?? profiles
-
- if (!profileData || !proxiesData || !profileData.items) {
- debugLog('[ActivateSelected] 代理或配置数据不可用,跳过处理')
- return
- }
-
- const current = profileData.items?.find(
- (e) => e && e.uid === profileData.current,
- )
-
- if (!current) {
- debugLog('[ActivateSelected] 未找到当前profile配置')
- return
- }
-
- // 检查是否有saved的代理选择
- const { selected = [] } = current
- if (selected.length === 0) {
- debugLog('[ActivateSelected] 当前profile无保存的代理选择,跳过')
- return
- }
-
- debugLog(
- `[ActivateSelected] 当前profile有 ${selected.length} 个代理选择配置`,
- )
-
- type SelectedEntry = { name?: string; now?: string }
- const selectedMap = Object.fromEntries(
- (selected as SelectedEntry[])
- .filter(
- (each): each is SelectedEntry & { name: string; now: string } =>
- each.name != null && each.now != null,
- )
- .map((each) => [each.name, each.now]),
- )
-
- let hasChange = false
- const newSelected: typeof selected = []
- const { global, groups } = proxiesData
- const selectableTypes = new Set([
- 'Selector',
- 'URLTest',
- 'Fallback',
- 'LoadBalance',
- ])
-
- // 处理所有代理组
- for (const group of [global, ...groups]) {
- if (!group) {
- continue
- }
-
- const { type, name, now } = group
- const savedProxy = selectedMap[name]
- const availableProxies = Array.isArray(group.all) ? group.all : []
-
- if (!selectableTypes.has(type)) {
- if (savedProxy != null || now != null) {
- const preferredProxy = now ? now : savedProxy
- newSelected.push({ name, now: preferredProxy })
- }
- continue
- }
-
- if (savedProxy == null) {
- if (now != null) {
- newSelected.push({ name, now })
- }
- continue
- }
-
- const existsInGroup = availableProxies.some((proxy) => {
- if (typeof proxy === 'string') {
- return proxy === savedProxy
- }
-
- return proxy?.name === savedProxy
- })
-
- if (!existsInGroup) {
- console.warn(
- `[ActivateSelected] 保存的代理 ${savedProxy} 不存在于代理组 ${name}`,
- )
- hasChange = true
- newSelected.push({ name, now: now ?? savedProxy })
- continue
- }
-
- if (savedProxy !== now) {
- debugLog(
- `[ActivateSelected] 需要切换代理组 ${name}: ${now} -> ${savedProxy}`,
- )
- hasChange = true
- try {
- await selectNodeForGroup(name, savedProxy)
- } catch (error: unknown) {
- console.warn(
- `[ActivateSelected] 切换代理组 ${name} 失败:`,
- error instanceof Error ? error.message : String(error),
- )
- }
- }
-
- newSelected.push({ name, now: savedProxy })
- }
-
- if (!hasChange) {
- debugLog('[ActivateSelected] 所有代理选择已经是目标状态,无需更新')
- return
- }
-
- debugLog(`[ActivateSelected] 完成代理切换,保存新的选择配置`)
-
- try {
- await patchProfile(current.uid, { selected: newSelected })
- debugLog('[ActivateSelected] 代理选择配置保存成功')
-
- setCacheData(['getProxies'], await calcuProxies())
- } catch (error: unknown) {
- console.error(
- '[ActivateSelected] 保存代理选择配置失败:',
- error instanceof Error ? error.message : String(error),
- )
- }
- } catch (error: unknown) {
- console.error(
- '[ActivateSelected] 处理代理选择失败:',
- error instanceof Error ? error.message : String(error),
- )
+ mutateProfiles()
}
}
return {
profiles,
current: profiles?.items?.find((p) => p && p.uid === profiles.current),
- activateSelected,
patchProfiles,
patchCurrent,
mutateProfiles,
diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx
index 03b79339a..ad4bf6adc 100644
--- a/src/pages/profiles.tsx
+++ b/src/pages/profiles.tsx
@@ -1,7 +1,7 @@
import {
closestCenter,
DndContext,
- DragEndEvent,
+ type DragEndEvent,
DragOverlay,
KeyboardSensor,
PointerSensor,
@@ -36,23 +36,23 @@ import {
} from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation } from 'react-router'
-import {
- closeAllConnections,
- selectNodeForGroup,
-} from 'tauri-plugin-mihomo-api'
+import { closeAllConnections } from 'tauri-plugin-mihomo-api'
-import { BasePage, BaseStyledTextField, DialogRef } from '@/components/base'
+import {
+ BasePage,
+ BaseStyledTextField,
+ type DialogRef,
+} from '@/components/base'
import { ProfileItem } from '@/components/profile/profile-item'
import { ProfileMore } from '@/components/profile/profile-more'
import {
ProfileViewer,
- ProfileViewerRef,
+ type ProfileViewerRef,
} from '@/components/profile/profile-viewer'
import { ConfigViewer } from '@/components/setting/mods/config-viewer'
import { useListen } from '@/hooks/use-listen'
import { useProfiles } from '@/hooks/use-profiles'
import {
- calcuProxies,
createProfile,
deleteProfile,
enhanceProfiles,
@@ -67,7 +67,6 @@ import { showNotice } from '@/services/notice-service'
import {
fetchCacheData,
revalidateQueries,
- setCacheData,
useQuery,
} from '@/services/query-client'
import {
@@ -466,22 +465,6 @@ const ProfilePage = () => {
return
}
- // 选择所记忆的节点
- const current = profiles.items?.find((e) => e.uid === profile)
- for (const item of current?.selected ?? []) {
- if (item.name && item.now) {
- try {
- await selectNodeForGroup(item.name, item.now)
- } catch (err) {
- debugLog(
- `[Profile] 选择节点失败: ${item.name} -> ${item.now}`,
- err,
- )
- }
- }
- }
- setCacheData(['getProxies'], await calcuProxies())
-
// 完成切换
await mutateLogs()
closeAllConnections()