ソースを参照

feat: refactor

GyDi 2 年 前
コミット
7f6dac4271

+ 29 - 21
src-tauri/src/cmds.rs

@@ -5,6 +5,7 @@ use crate::{
 use crate::{log_if_err, ret_err, wrap_err};
 use anyhow::Result;
 use serde_yaml::Mapping;
+use std::collections::HashMap;
 use tauri::{api, State};
 
 type CmdResult<T = ()> = Result<T, String>;
@@ -19,7 +20,6 @@ pub fn get_profiles(core: State<'_, Core>) -> CmdResult<Profiles> {
 /// manually exec enhanced profile
 #[tauri::command]
 pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult {
-  // wrap_err!(core.activate_enhanced(false))
   wrap_err!(core.activate())
 }
 
@@ -59,7 +59,7 @@ pub async fn update_profile(
   option: Option<PrfOption>,
   core: State<'_, Core>,
 ) -> CmdResult {
-  wrap_err!(Core::update_profile_item(core.inner().clone(), index, option).await)
+  wrap_err!(core.update_profile_item(index, option).await)
 }
 
 /// change the current profile
@@ -67,10 +67,7 @@ pub async fn update_profile(
 pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult {
   let mut profiles = core.profiles.lock();
   wrap_err!(profiles.put_current(index))?;
-
   drop(profiles);
-
-  // wrap_err!(core.activate_enhanced(false))
   wrap_err!(core.activate())
 }
 
@@ -79,10 +76,7 @@ pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult {
 pub fn change_profile_chain(chain: Option<Vec<String>>, core: State<'_, Core>) -> CmdResult {
   let mut profiles = core.profiles.lock();
   wrap_err!(profiles.put_chain(chain))?;
-
   drop(profiles);
-
-  // wrap_err!(core.activate_enhanced(false))
   wrap_err!(core.activate())
 }
 
@@ -91,10 +85,7 @@ pub fn change_profile_chain(chain: Option<Vec<String>>, core: State<'_, Core>) -
 pub fn change_profile_valid(valid: Option<Vec<String>>, core: State<Core>) -> CmdResult {
   let mut profiles = core.profiles.lock();
   wrap_err!(profiles.put_valid(valid))?;
-
   drop(profiles);
-
-  // wrap_err!(core.activate_enhanced(false))
   wrap_err!(core.activate())
 }
 
@@ -102,14 +93,10 @@ pub fn change_profile_valid(valid: Option<Vec<String>>, core: State<Core>) -> Cm
 #[tauri::command]
 pub fn delete_profile(index: String, core: State<'_, Core>) -> CmdResult {
   let mut profiles = core.profiles.lock();
-
   if wrap_err!(profiles.delete_item(index))? {
     drop(profiles);
-
-    // log_if_err!(core.activate_enhanced(false));
     log_if_err!(core.activate());
   }
-
   Ok(())
 }
 
@@ -148,10 +135,8 @@ pub fn view_profile(index: String, core: State<'_, Core>) -> CmdResult {
 #[tauri::command]
 pub fn read_profile_file(index: String, core: State<'_, Core>) -> CmdResult<String> {
   let profiles = core.profiles.lock();
-
   let item = wrap_err!(profiles.get_item(&index))?;
   let data = wrap_err!(item.read_file())?;
-
   Ok(data)
 }
 
@@ -179,11 +164,34 @@ pub fn get_clash_info(core: State<'_, Core>) -> CmdResult<ClashInfo> {
   Ok(clash.info.clone())
 }
 
-/// get the running clash config string
+/// get the runtime clash config mapping
 #[tauri::command]
-pub fn get_running_config(core: State<'_, Core>) -> CmdResult<Option<String>> {
-  let clash = core.clash.lock();
-  Ok(clash.running_config.clone())
+pub fn get_runtime_config(core: State<'_, Core>) -> CmdResult<Option<Mapping>> {
+  let rt = core.runtime.lock();
+  Ok(rt.config.clone())
+}
+
+/// get the runtime clash config yaml string
+#[tauri::command]
+pub fn get_runtime_yaml(core: State<'_, Core>) -> CmdResult<Option<String>> {
+  let rt = core.runtime.lock();
+  Ok(rt.config_yaml.clone())
+}
+
+/// get the runtime config exists keys
+#[tauri::command]
+pub fn get_runtime_exists(core: State<'_, Core>) -> CmdResult<Vec<String>> {
+  let rt = core.runtime.lock();
+  Ok(rt.exists_keys.clone())
+}
+
+/// get the runtime enhanced chain log
+#[tauri::command]
+pub fn get_runtime_logs(
+  core: State<'_, Core>,
+) -> CmdResult<HashMap<String, Vec<(String, String)>>> {
+  let rt = core.runtime.lock();
+  Ok(rt.chain_logs.clone())
 }
 
 /// update the clash core config

+ 29 - 8
src-tauri/src/config/field.rs

@@ -63,19 +63,28 @@ pub fn use_valid_fields(mut valid: Vec<String>) -> Vec<String> {
     .collect()
 }
 
-pub fn use_filter(config: Mapping, filter: Vec<String>) -> Mapping {
+pub fn use_filter(config: Mapping, filter: &Vec<String>) -> Mapping {
   let mut ret = Mapping::new();
 
   for (key, value) in config.into_iter() {
-    key.as_str().map(|key_str| {
-      // change to lowercase
+    if let Some(key) = key.as_str() {
+      if filter.contains(&key.to_string()) {
+        ret.insert(Value::from(key), value);
+      }
+    }
+  }
+  ret
+}
+
+pub fn use_lowercase(config: Mapping) -> Mapping {
+  let mut ret = Mapping::new();
+
+  for (key, value) in config.into_iter() {
+    if let Some(key_str) = key.as_str() {
       let mut key_str = String::from(key_str);
       key_str.make_ascii_lowercase();
-
-      if filter.contains(&key_str) {
-        ret.insert(Value::from(key_str), value);
-      }
-    });
+      ret.insert(Value::from(key_str), value);
+    }
   }
   ret
 }
@@ -95,3 +104,15 @@ pub fn use_sort(config: Mapping) -> Mapping {
     });
   ret
 }
+
+pub fn use_keys(config: &Mapping) -> Vec<String> {
+  config
+    .iter()
+    .filter_map(|(key, _)| key.as_str())
+    .map(|s| {
+      let mut s = s.to_string();
+      s.make_ascii_lowercase();
+      return s;
+    })
+    .collect()
+}

+ 9 - 15
src-tauri/src/config/merge.rs

@@ -1,4 +1,4 @@
-use super::{use_filter, use_valid_fields};
+use super::{use_filter, use_lowercase};
 use serde_yaml::{self, Mapping, Sequence, Value};
 
 #[allow(unused)]
@@ -11,17 +11,16 @@ const MERGE_FIELDS: [&str; 6] = [
   "append-proxy-groups",
 ];
 
-pub fn use_merge(merge: Mapping, mut config: Mapping, valid: Vec<String>) -> Mapping {
-  let valid_list = use_valid_fields(valid);
-  let merge_valid = use_filter(merge.clone(), valid_list);
-
+pub fn use_merge(merge: Mapping, mut config: Mapping) -> Mapping {
   // 直接覆盖原字段
-  merge_valid.into_iter().for_each(|(key, value)| {
-    config.insert(key, value);
-  });
+  use_lowercase(merge.clone())
+    .into_iter()
+    .for_each(|(key, value)| {
+      config.insert(key, value);
+    });
 
   let merge_list = MERGE_FIELDS.iter().map(|s| s.to_string());
-  let merge = use_filter(merge, merge_list.collect());
+  let merge = use_filter(merge, &merge_list.collect());
 
   ["rules", "proxies", "proxy-groups"]
     .iter()
@@ -52,7 +51,6 @@ pub fn use_merge(merge: Mapping, mut config: Mapping, valid: Vec<String>) -> Map
 
       config.insert(key_val, Value::from(list));
     });
-
   config
 }
 
@@ -87,11 +85,7 @@ fn test_merge() -> anyhow::Result<()> {
   let merge = serde_yaml::from_str::<Mapping>(merge)?;
   let config = serde_yaml::from_str::<Mapping>(config)?;
 
-  let result = serde_yaml::to_string(&use_merge(
-    merge,
-    config,
-    vec!["tun"].iter().map(|s| s.to_string()).collect(),
-  ))?;
+  let result = serde_yaml::to_string(&use_merge(merge, config))?;
 
   println!("{result}");
 

+ 29 - 19
src-tauri/src/config/mod.rs

@@ -7,53 +7,63 @@ pub(self) use self::field::*;
 use self::merge::*;
 use self::script::*;
 use self::tun::*;
-use crate::core::PrfData;
+use crate::core::ChainItem;
+use crate::core::ChainType;
 use serde_yaml::Mapping;
 use std::collections::HashMap;
+use std::collections::HashSet;
 
 type ResultLog = Vec<(String, String)>;
 
-pub fn runtime_config(
+pub fn enhance_config(
   clash_config: Mapping,
   profile_config: Mapping,
-  profile_enhanced: Vec<PrfData>,
+  chain: Vec<ChainItem>,
   valid: Vec<String>,
   tun_mode: bool,
-) -> (Mapping, HashMap<String, ResultLog>) {
+) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
   let mut config = profile_config;
   let mut result_map = HashMap::new();
+  let mut exists_keys = use_keys(&config);
 
-  profile_enhanced.into_iter().for_each(|data| {
-    if data.merge.is_some() {
-      config = use_merge(data.merge.unwrap(), config.to_owned(), valid.clone());
-    } else if data.script.is_some() {
+  let valid = use_valid_fields(valid);
+
+  chain.into_iter().for_each(|item| match item.data {
+    ChainType::Merge(merge) => {
+      exists_keys.extend(use_keys(&merge));
+      config = use_merge(merge, config.to_owned());
+      config = use_filter(config.to_owned(), &valid);
+    }
+    ChainType::Script(script) => {
       let mut logs = vec![];
 
-      match use_script(data.script.unwrap(), config.to_owned(), valid.clone()) {
+      match use_script(script, config.to_owned()) {
         Ok((res_config, res_logs)) => {
-          config = res_config;
+          exists_keys.extend(use_keys(&res_config));
+          config = use_filter(res_config, &valid);
           logs.extend(res_logs);
         }
-        Err(err) => {
-          logs.push(("error".into(), err.to_string()));
-        }
+        Err(err) => logs.push(("exception".into(), err.to_string())),
       }
 
-      if let Some(uid) = data.item.uid {
-        result_map.insert(uid, logs);
-      }
+      result_map.insert(item.uid, logs);
     }
   });
 
-  config = use_filter(config, use_valid_fields(valid));
+  config = use_filter(config, &valid);
 
   for (key, value) in clash_config.into_iter() {
     config.insert(key, value);
   }
 
-  config = use_filter(config, use_clash_fields());
+  let clash_fields = use_clash_fields();
+  config = use_filter(config, &clash_fields);
   config = use_tun(config, tun_mode);
   config = use_sort(config);
 
-  (config, result_map)
+  let mut exists_set = HashSet::new();
+  exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s)));
+  exists_keys = exists_set.into_iter().collect();
+
+  (config, exists_keys, result_map)
 }

+ 19 - 21
src-tauri/src/config/script.rs

@@ -1,12 +1,8 @@
-use super::{use_filter, use_valid_fields};
+use super::use_lowercase;
 use anyhow::Result;
-use serde_yaml::{self, Mapping};
+use serde_yaml::Mapping;
 
-pub fn use_script(
-  script: String,
-  config: Mapping,
-  valid: Vec<String>,
-) -> Result<(Mapping, Vec<(String, String)>)> {
+pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
   use rquickjs::{Context, Func, Runtime};
   use std::sync::{Arc, Mutex};
 
@@ -33,25 +29,32 @@ pub fn use_script(
       });"#,
     )?;
 
+    let config = use_lowercase(config.clone());
     let config_str = serde_json::to_string(&config)?;
 
-    let code = format!("\n{script}\n;\nJSON.stringify(main({config_str})||'')");
+    let code = format!(
+      r#"try{{
+        {script}\n;
+        JSON.stringify(main({config_str})||'')
+      }} catch(err) {{
+        `__error_flag__ ${{err.toString()}}`
+      }}"#
+    );
     let result: String = ctx.eval(code.as_str())?;
+    // if result.starts_with("__error_flag__") {
+    //   anyhow::bail!(result.slice_unchecked(begin, end));
+    // }
     if result == "\"\"" {
       anyhow::bail!("main function should return object");
     }
-    Ok(serde_json::from_str::<Mapping>(result.as_str())?)
+    return Ok(serde_json::from_str::<Mapping>(result.as_str())?);
   });
 
   let mut out = outputs.lock().unwrap();
   match result {
-    Ok(config) => {
-      let valid = use_valid_fields(valid);
-      let config = use_filter(config, valid);
-      Ok((config, out.to_vec()))
-    }
+    Ok(config) => Ok((use_lowercase(config), out.to_vec())),
     Err(err) => {
-      out.push(("error".into(), err.to_string()));
+      out.push(("exception".into(), err.to_string()));
       Ok((config, out.to_vec()))
     }
   }
@@ -81,12 +84,7 @@ fn test_script() {
   "#;
 
   let config = serde_yaml::from_str(config).unwrap();
-  let (config, results) = use_script(
-    script.into(),
-    config,
-    vec!["tun"].iter().map(|s| s.to_string()).collect(),
-  )
-  .unwrap();
+  let (config, results) = use_script(script.into(), config).unwrap();
 
   let config_str = serde_yaml::to_string(&config).unwrap();
 

+ 1 - 16
src-tauri/src/core/clash.rs

@@ -87,9 +87,6 @@ pub struct Clash {
 
   /// some info
   pub info: ClashInfo,
-
-  /// save the running config
-  pub running_config: Option<String>,
 }
 
 impl Clash {
@@ -97,11 +94,7 @@ impl Clash {
     let config = Clash::read_config();
     let info = ClashInfo::from(&config);
 
-    Clash {
-      config,
-      info,
-      running_config: None,
-    }
+    Clash { config, info }
   }
 
   /// get clash config
@@ -118,14 +111,6 @@ impl Clash {
     )
   }
 
-  /// save running config
-  pub fn set_running_config(&mut self, config: &Mapping) {
-    self.running_config = match serde_yaml::to_string(config) {
-      Ok(config_str) => Some(config_str),
-      Err(_) => None,
-    };
-  }
-
   /// patch update the clash config
   /// if the port is changed then return true
   pub fn patch_config(&mut self, patch: Mapping) -> Result<(bool, bool)> {

+ 0 - 66
src-tauri/src/core/enhance.rs

@@ -1,66 +0,0 @@
-use super::prfitem::PrfItem;
-use crate::utils::{config, dirs};
-use serde::{Deserialize, Serialize};
-use serde_yaml::Mapping;
-use std::fs;
-
-#[derive(Default, Debug, Clone, Serialize, Deserialize)]
-pub struct PrfEnhanced {
-  pub current: Mapping,
-
-  pub chain: Vec<PrfData>,
-
-  pub valid: Vec<String>,
-
-  pub callback: String,
-}
-
-#[derive(Default, Debug, Clone, Serialize, Deserialize)]
-pub struct PrfEnhancedResult {
-  pub data: Option<Mapping>,
-
-  pub status: String,
-
-  pub error: Option<String>,
-}
-
-#[derive(Default, Debug, Clone, Serialize, Deserialize)]
-pub struct PrfData {
-  pub item: PrfItem,
-
-  #[serde(skip_serializing_if = "Option::is_none")]
-  pub merge: Option<Mapping>,
-
-  #[serde(skip_serializing_if = "Option::is_none")]
-  pub script: Option<String>,
-}
-
-impl PrfData {
-  pub fn from_item(item: &PrfItem) -> Option<PrfData> {
-    match item.itype.as_ref() {
-      Some(itype) => {
-        let file = item.file.clone()?;
-        let path = dirs::app_profiles_dir().join(file);
-
-        if !path.exists() {
-          return None;
-        }
-
-        match itype.as_str() {
-          "script" => Some(PrfData {
-            item: item.clone(),
-            script: Some(fs::read_to_string(path).unwrap_or("".into())),
-            merge: None,
-          }),
-          "merge" => Some(PrfData {
-            item: item.clone(),
-            merge: Some(config::read_yaml::<Mapping>(path)),
-            script: None,
-          }),
-          _ => None,
-        }
-      }
-      None => None,
-    }
-  }
-}

+ 31 - 170
src-tauri/src/core/mod.rs

@@ -1,21 +1,16 @@
 use self::notice::Notice;
 use self::sysopt::Sysopt;
 use self::timer::Timer;
-use crate::config::runtime_config;
-use crate::core::enhance::PrfEnhancedResult;
+use crate::config::enhance_config;
 use crate::log_if_err;
-use crate::utils::help;
 use anyhow::{bail, Result};
 use parking_lot::Mutex;
 use serde_yaml::Mapping;
 use serde_yaml::Value;
 use std::sync::Arc;
-use std::time::Duration;
 use tauri::{AppHandle, Manager, Window};
-use tokio::time::sleep;
 
 mod clash;
-mod enhance;
 mod notice;
 mod prfitem;
 mod profiles;
@@ -25,47 +20,33 @@ mod timer;
 mod verge;
 
 pub use self::clash::*;
-pub use self::enhance::*;
 pub use self::prfitem::*;
 pub use self::profiles::*;
 pub use self::service::*;
 pub use self::verge::*;
 
-/// close the window for slient start
-/// after enhance mode
-static mut WINDOW_CLOSABLE: bool = true;
-
 #[derive(Clone)]
 pub struct Core {
   pub clash: Arc<Mutex<Clash>>,
-
   pub verge: Arc<Mutex<Verge>>,
-
   pub profiles: Arc<Mutex<Profiles>>,
-
   pub service: Arc<Mutex<Service>>,
-
   pub sysopt: Arc<Mutex<Sysopt>>,
-
   pub timer: Arc<Mutex<Timer>>,
-
+  pub runtime: Arc<Mutex<RuntimeResult>>,
   pub window: Arc<Mutex<Option<Window>>>,
 }
 
 impl Core {
   pub fn new() -> Core {
-    let clash = Clash::new();
-    let verge = Verge::new();
-    let profiles = Profiles::new();
-    let service = Service::new();
-
     Core {
-      clash: Arc::new(Mutex::new(clash)),
-      verge: Arc::new(Mutex::new(verge)),
-      profiles: Arc::new(Mutex::new(profiles)),
-      service: Arc::new(Mutex::new(service)),
+      clash: Arc::new(Mutex::new(Clash::new())),
+      verge: Arc::new(Mutex::new(Verge::new())),
+      profiles: Arc::new(Mutex::new(Profiles::new())),
+      service: Arc::new(Mutex::new(Service::new())),
       sysopt: Arc::new(Mutex::new(Sysopt::new())),
       timer: Arc::new(Mutex::new(Timer::new())),
+      runtime: Arc::new(Mutex::new(RuntimeResult::default())),
       window: Arc::new(Mutex::new(None)),
     }
   }
@@ -95,15 +76,6 @@ impl Core {
 
     // let silent_start = verge.enable_silent_start.clone();
     let auto_launch = verge.enable_auto_launch.clone();
-
-    // silent start
-    // if silent_start.unwrap_or(false) {
-    //   let window = self.window.lock();
-    //   window.as_ref().map(|win| {
-    //     win.hide().unwrap();
-    //   });
-    // }
-
     let mut sysopt = self.sysopt.lock();
 
     sysopt.init_sysproxy(clash.info.port.clone(), &verge);
@@ -116,13 +88,6 @@ impl Core {
     log_if_err!(self.update_systray(&app_handle));
     log_if_err!(self.update_systray_clash(&app_handle));
 
-    // // wait the window setup during resolve app
-    // let core = self.clone();
-    // tauri::async_runtime::spawn(async move {
-    //   sleep(Duration::from_secs(2)).await;
-    //   log_if_err!(core.activate_enhanced(true));
-    // });
-
     // timer initialize
     let mut timer = self.timer.lock();
     timer.set_core(self.clone());
@@ -140,9 +105,7 @@ impl Core {
     let mut service = self.service.lock();
     service.restart()?;
     drop(service);
-
     self.activate()
-    // self.activate_enhanced(true)
   }
 
   /// change the clash core
@@ -167,7 +130,6 @@ impl Core {
     drop(service);
 
     self.activate()
-    // self.activate_enhanced(true)
   }
 
   /// Patch Clash
@@ -186,7 +148,6 @@ impl Core {
       drop(service);
 
       self.activate()?;
-      // self.activate_enhanced(true)?;
 
       let mut sysopt = self.sysopt.lock();
       let verge = self.verge.lock();
@@ -260,7 +221,6 @@ impl Core {
     }
 
     if tun_mode.is_some() {
-      // self.activate_enhanced(false)?;
       self.activate()?;
     }
 
@@ -345,33 +305,34 @@ impl Core {
   /// activate the profile
   /// auto activate enhanced profile
   pub fn activate(&self) -> Result<()> {
-    let profiles = self.profiles.lock();
-    let profile_config = profiles.gen_activate()?;
-    let profile_enhanced = profiles.gen_enhanced("".into())?;
-    drop(profiles);
+    let profile_activate = {
+      let profiles = self.profiles.lock();
+      profiles.gen_activate()?
+    };
+
+    let (clash_config, clash_info) = {
+      let clash = self.clash.lock();
+      (clash.config.clone(), clash.info.clone())
+    };
 
     let tun_mode = {
       let verge = self.verge.lock();
       verge.enable_tun_mode.unwrap_or(false)
     };
 
-    let mut clash = self.clash.lock();
-    let clash_config = clash.config.clone();
-
-    let (config, result) = runtime_config(
+    let (config, exists_keys, logs) = enhance_config(
       clash_config,
-      profile_config,
-      profile_enhanced.chain,
-      profile_enhanced.valid,
+      profile_activate.current,
+      profile_activate.chain,
+      profile_activate.valid,
       tun_mode,
     );
 
-    dbg!(result);
-
-    let info = clash.info.clone();
-
-    clash.set_running_config(&config);
-    drop(clash);
+    let mut runtime = self.runtime.lock();
+    runtime.config = Some(config.clone());
+    runtime.config_yaml = Some(serde_yaml::to_string(&config).unwrap_or("".into()));
+    runtime.exists_keys = exists_keys;
+    runtime.chain_logs = logs;
 
     let notice = {
       let window = self.window.lock();
@@ -379,109 +340,14 @@ impl Core {
     };
 
     let service = self.service.lock();
-    service.set_config(info, config, notice)
+    service.set_config(clash_info, config, notice)
   }
 
-  // /// Enhanced
-  // /// enhanced profiles mode
-  // pub fn activate_enhanced(&self, skip: bool) -> Result<()> {
-  //   let window = self.window.lock();
-  //   if window.is_none() {
-  //     bail!("failed to get the main window");
-  //   }
-
-  //   let event_name = help::get_uid("e");
-  //   let event_name = format!("enhanced-cb-{event_name}");
-
-  //   // generate the payload
-  //   let payload = {
-  //     let profiles = self.profiles.lock();
-  //     profiles.gen_enhanced(event_name.clone())?
-  //   };
-
-  //   // do not run enhanced
-  //   if payload.chain.len() == 0 {
-  //     if skip {
-  //       return Ok(());
-  //     }
-
-  //     drop(window);
-  //     return self.activate();
-  //   }
-
-  //   let tun_mode = {
-  //     let verge = self.verge.lock();
-  //     verge.enable_tun_mode.unwrap_or(false)
-  //   };
-
-  //   let info = {
-  //     let clash = self.clash.lock();
-  //     clash.info.clone()
-  //   };
-
-  //   let notice = Notice::from(window.clone());
-  //   let service = self.service.clone();
-
-  //   let window = window.clone().unwrap();
-  //   window.once(&event_name, move |event| {
-  //     let result = event.payload();
-
-  //     if result.is_none() {
-  //       log::warn!(target: "app", "event payload result is none");
-  //       return;
-  //     }
-
-  //     let result = result.unwrap();
-  //     let result: PrfEnhancedResult = serde_json::from_str(result).unwrap();
-
-  //     if let Some(data) = result.data {
-  //       let mut config = Clash::read_config();
-  //       let filter_data = Clash::loose_filter(data); // loose filter
-
-  //       for (key, value) in filter_data.into_iter() {
-  //         config.insert(key, value);
-  //       }
-
-  //       let config = Clash::_tun_mode(config, tun_mode);
-
-  //       let service = service.lock();
-  //       log_if_err!(service.set_config(info, config, notice));
-
-  //       log::info!(target: "app", "profile enhanced status {}", result.status);
-  //     }
-
-  //     result.error.map(|err| log::error!(target: "app", "{err}"));
-  //   });
-
-  //   let verge = self.verge.lock();
-  //   let silent_start = verge.enable_silent_start.clone();
-
-  //   let closable = unsafe { WINDOW_CLOSABLE };
-
-  //   if silent_start.unwrap_or(false) && closable {
-  //     unsafe {
-  //       WINDOW_CLOSABLE = false;
-  //     }
-
-  //     window.emit("script-handler-close", payload).unwrap();
-  //   } else {
-  //     window.emit("script-handler", payload).unwrap();
-  //   }
-
-  //   Ok(())
-  // }
-}
-
-impl Core {
   /// Static function
   /// update profile item
-  pub async fn update_profile_item(
-    core: Core,
-    uid: String,
-    option: Option<PrfOption>,
-  ) -> Result<()> {
+  pub async fn update_profile_item(&self, uid: String, option: Option<PrfOption>) -> Result<()> {
     let (url, opt) = {
-      let profiles = core.profiles.lock();
+      let profiles = self.profiles.lock();
       let item = profiles.get_item(&uid)?;
 
       if let Some(typ) = item.itype.as_ref() {
@@ -490,32 +356,27 @@ impl Core {
           // reactivate the config
           if Some(uid) == profiles.get_current() {
             drop(profiles);
-            // return core.activate_enhanced(false);
-            return core.activate();
+            return self.activate();
           }
-
           return Ok(());
         }
       }
-
       if item.url.is_none() {
         bail!("failed to get the profile item url");
       }
-
       (item.url.clone().unwrap(), item.option.clone())
     };
 
     let merged_opt = PrfOption::merge(opt, option);
     let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
 
-    let mut profiles = core.profiles.lock();
+    let mut profiles = self.profiles.lock();
     profiles.update_item(uid.clone(), item)?;
 
     // reactivate the profile
     if Some(uid) == profiles.get_current() {
       drop(profiles);
-      // core.activate_enhanced(false)?;
-      core.activate()?;
+      self.activate()?;
     }
 
     Ok(())

+ 2 - 0
src-tauri/src/core/notice.rs

@@ -11,6 +11,7 @@ impl Notice {
     Notice { win }
   }
 
+  #[allow(unused)]
   pub fn set_win(&mut self, win: Option<Window>) {
     self.win = win;
   }
@@ -27,6 +28,7 @@ impl Notice {
     }
   }
 
+  #[allow(unused)]
   pub fn refresh_profiles(&self) {
     if let Some(window) = self.win.as_ref() {
       log_if_err!(window.emit("verge://refresh-profiles-config", "yes"));

+ 37 - 1
src-tauri/src/core/prfitem.rs

@@ -1,4 +1,4 @@
-use crate::utils::{dirs, help, tmpl};
+use crate::utils::{config, dirs, help, tmpl};
 use anyhow::{bail, Context, Result};
 use serde::{Deserialize, Serialize};
 use serde_yaml::Mapping;
@@ -333,4 +333,40 @@ impl PrfItem {
     let path = dirs::app_profiles_dir().join(file);
     fs::write(path, data.as_bytes()).context("failed to save the file")
   }
+
+  /// get the data for enhanced mode
+  pub fn to_enhance(&self) -> Option<ChainItem> {
+    let itype = self.itype.as_ref()?.as_str();
+    let file = self.file.clone()?;
+    let uid = self.uid.clone().unwrap_or("".into());
+    let path = dirs::app_profiles_dir().join(file);
+
+    if !path.exists() {
+      return None;
+    }
+
+    match itype {
+      "script" => Some(ChainItem {
+        uid,
+        data: ChainType::Script(fs::read_to_string(path).unwrap_or("".into())),
+      }),
+      "merge" => Some(ChainItem {
+        uid,
+        data: ChainType::Merge(config::read_yaml::<Mapping>(path)),
+      }),
+      _ => None,
+    }
+  }
+}
+
+#[derive(Debug, Clone)]
+pub struct ChainItem {
+  pub uid: String,
+  pub data: ChainType,
+}
+
+#[derive(Debug, Clone)]
+pub enum ChainType {
+  Merge(Mapping),
+  Script(String),
 }

+ 28 - 19
src-tauri/src/core/profiles.rs

@@ -1,9 +1,10 @@
-use super::enhance::{PrfData, PrfEnhanced};
 use super::prfitem::PrfItem;
+use super::ChainItem;
 use crate::utils::{config, dirs, help};
 use anyhow::{bail, Context, Result};
 use serde::{Deserialize, Serialize};
 use serde_yaml::Mapping;
+use std::collections::HashMap;
 use std::{fs, io::Write};
 
 ///
@@ -262,8 +263,8 @@ impl Profiles {
     Ok(current == uid)
   }
 
-  /// only generate config mapping
-  pub fn gen_activate(&self) -> Result<Mapping> {
+  /// generate the current Mapping data
+  fn gen_current(&self) -> Result<Mapping> {
     let config = Mapping::new();
 
     if self.current.is_none() || self.items.is_none() {
@@ -271,7 +272,6 @@ impl Profiles {
     }
 
     let current = self.current.clone().unwrap();
-
     for item in self.items.as_ref().unwrap().iter() {
       if item.uid == Some(current.clone()) {
         let file_path = match item.file.clone() {
@@ -286,34 +286,43 @@ impl Profiles {
         return Ok(config::read_yaml::<Mapping>(file_path.clone()));
       }
     }
-
     bail!("failed to found the uid \"{current}\"");
   }
 
-  /// gen the enhanced profiles
-  pub fn gen_enhanced(&self, callback: String) -> Result<PrfEnhanced> {
-    let current = self.gen_activate()?;
-
+  /// generate the data for activate clash config
+  pub fn gen_activate(&self) -> Result<PrfActivate> {
+    let current = self.gen_current()?;
     let chain = match self.chain.as_ref() {
       Some(chain) => chain
         .iter()
-        .map(|uid| self.get_item(uid))
-        .filter(|item| item.is_ok())
-        .map(|item| item.unwrap())
-        .map(|item| PrfData::from_item(item))
-        .filter(|o| o.is_some())
-        .map(|o| o.unwrap())
-        .collect::<Vec<PrfData>>(),
+        .filter_map(|uid| self.get_item(uid).ok())
+        .filter_map(|item| item.to_enhance())
+        .collect::<Vec<ChainItem>>(),
       None => vec![],
     };
-
     let valid = self.valid.clone().unwrap_or(vec![]);
 
-    Ok(PrfEnhanced {
+    Ok(PrfActivate {
       current,
       chain,
       valid,
-      callback,
     })
   }
 }
+
+#[derive(Default, Clone)]
+pub struct PrfActivate {
+  pub current: Mapping,
+  pub chain: Vec<ChainItem>,
+  pub valid: Vec<String>,
+}
+
+#[derive(Default, Debug, Clone, Deserialize, Serialize)]
+pub struct RuntimeResult {
+  pub config: Option<Mapping>,
+  pub config_yaml: Option<String>,
+  // 记录在配置中(包括merge和script生成的)出现过的keys
+  // 这些keys不一定都生效
+  pub exists_keys: Vec<String>,
+  pub chain_logs: HashMap<String, Vec<(String, String)>>,
+}

+ 1 - 1
src-tauri/src/core/timer.rs

@@ -140,7 +140,7 @@ impl Timer {
   /// the task runner
   async fn async_task(core: Core, uid: String) {
     log::info!(target: "app", "running timer task `{uid}`");
-    log_if_err!(Core::update_profile_item(core, uid, None).await);
+    log_if_err!(core.update_profile_item(uid, None).await);
   }
 }
 

+ 4 - 1
src-tauri/src/main.rs

@@ -116,7 +116,10 @@ fn main() -> std::io::Result<()> {
       cmds::get_clash_info,
       cmds::patch_clash_config,
       cmds::change_clash_core,
-      cmds::get_running_config,
+      cmds::get_runtime_config,
+      cmds::get_runtime_yaml,
+      cmds::get_runtime_exists,
+      cmds::get_runtime_logs,
       // verge
       cmds::get_verge_config,
       cmds::patch_verge_config,

+ 0 - 11
src-tauri/src/states.rs

@@ -1,11 +0,0 @@
-// use crate::core::{Clash, Profiles, Verge};
-// use std::sync::{Arc, Mutex};
-
-// #[derive(Default)]
-// pub struct ProfilesState(pub Arc<Mutex<Profiles>>);
-
-// #[derive(Default)]
-// pub struct ClashState(pub Arc<Mutex<Clash>>);
-
-// #[derive(Default)]
-// pub struct VergeState(pub Arc<Mutex<Verge>>);

+ 11 - 107
src/components/profile/enhanced.tsx

@@ -1,29 +1,13 @@
 import useSWR from "swr";
-import { useState } from "react";
 import { useLockFn } from "ahooks";
-import {
-  Box,
-  Divider,
-  Grid,
-  IconButton,
-  ListItemIcon,
-  ListItemText,
-  Menu,
-  MenuItem,
-  Stack,
-} from "@mui/material";
-import {
-  AddchartRounded,
-  CheckRounded,
-  MenuRounded,
-  RestartAltRounded,
-} from "@mui/icons-material";
+import { Box, Grid, IconButton, Stack } from "@mui/material";
+import { RestartAltRounded } from "@mui/icons-material";
 import {
   getProfiles,
   deleteProfile,
   enhanceProfiles,
   changeProfileChain,
-  changeProfileValid,
+  getRuntimeLogs,
 } from "@/services/cmds";
 import ProfileMore from "./profile-more";
 import Notice from "../base/base-notice";
@@ -36,10 +20,8 @@ interface Props {
 const EnhancedMode = (props: Props) => {
   const { items, chain } = props;
 
-  const { data, mutate } = useSWR("getProfiles", getProfiles);
-  const valid = data?.valid || [];
-
-  const [anchorEl, setAnchorEl] = useState<any>(null);
+  const { mutate: mutateProfiles } = useSWR("getProfiles", getProfiles);
+  const { data: chainLogs = {} } = useSWR("getRuntimeLogs", getRuntimeLogs);
 
   // handler
   const onEnhance = useLockFn(async () => {
@@ -56,7 +38,7 @@ const EnhancedMode = (props: Props) => {
 
     const newChain = [...chain, uid];
     await changeProfileChain(newChain);
-    mutate((conf = {}) => ({ ...conf, chain: newChain }), true);
+    mutateProfiles((conf = {}) => ({ ...conf, chain: newChain }), true);
   });
 
   const onEnhanceDisable = useLockFn(async (uid: string) => {
@@ -64,14 +46,14 @@ const EnhancedMode = (props: Props) => {
 
     const newChain = chain.filter((i) => i !== uid);
     await changeProfileChain(newChain);
-    mutate((conf = {}) => ({ ...conf, chain: newChain }), true);
+    mutateProfiles((conf = {}) => ({ ...conf, chain: newChain }), true);
   });
 
   const onEnhanceDelete = useLockFn(async (uid: string) => {
     try {
       await onEnhanceDisable(uid);
       await deleteProfile(uid);
-      mutate();
+      mutateProfiles();
     } catch (err: any) {
       Notice.error(err?.message || err.toString());
     }
@@ -82,7 +64,7 @@ const EnhancedMode = (props: Props) => {
 
     const newChain = [uid].concat(chain.filter((i) => i !== uid));
     await changeProfileChain(newChain);
-    mutate((conf = {}) => ({ ...conf, chain: newChain }), true);
+    mutateProfiles((conf = {}) => ({ ...conf, chain: newChain }), true);
   });
 
   const onMoveEnd = useLockFn(async (uid: string) => {
@@ -90,20 +72,7 @@ const EnhancedMode = (props: Props) => {
 
     const newChain = chain.filter((i) => i !== uid).concat([uid]);
     await changeProfileChain(newChain);
-    mutate((conf = {}) => ({ ...conf, chain: newChain }), true);
-  });
-
-  // update valid list
-  const onToggleValid = useLockFn(async (key: string) => {
-    try {
-      const newValid = valid.includes(key)
-        ? valid.filter((i) => i !== key)
-        : valid.concat(key);
-      await changeProfileValid(newValid);
-      mutate();
-    } catch (err: any) {
-      Notice.error(err.message || err.toString());
-    }
+    mutateProfiles((conf = {}) => ({ ...conf, chain: newChain }), true);
   });
 
   return (
@@ -123,72 +92,6 @@ const EnhancedMode = (props: Props) => {
         >
           <RestartAltRounded />
         </IconButton>
-
-        <IconButton
-          size="small"
-          color="inherit"
-          id="profile-use-button"
-          title="enable clash fields"
-          aria-controls={!!anchorEl ? "profile-use-menu" : undefined}
-          aria-haspopup="true"
-          aria-expanded={!!anchorEl ? "true" : undefined}
-          onClick={(e) => setAnchorEl(e.currentTarget)}
-        >
-          <MenuRounded />
-        </IconButton>
-
-        <Menu
-          id="profile-use-menu"
-          open={!!anchorEl}
-          anchorEl={anchorEl}
-          onClose={() => setAnchorEl(null)}
-          transitionDuration={225}
-          MenuListProps={{
-            dense: true,
-            "aria-labelledby": "profile-use-button",
-          }}
-          onContextMenu={(e) => {
-            setAnchorEl(null);
-            e.preventDefault();
-          }}
-        >
-          <MenuItem>
-            <ListItemIcon color="inherit">
-              <AddchartRounded />
-            </ListItemIcon>
-            Use Clash Fields
-          </MenuItem>
-
-          <Divider />
-
-          {[
-            "tun",
-            "dns",
-            "hosts",
-            "script",
-            "profile",
-            "payload",
-            "interface-name",
-            "routing-mark",
-          ].map((key) => {
-            const has = valid.includes(key);
-
-            return (
-              <MenuItem
-                key={key}
-                sx={{ width: 180 }}
-                onClick={() => onToggleValid(key)}
-              >
-                {has && (
-                  <ListItemIcon color="inherit">
-                    <CheckRounded />
-                  </ListItemIcon>
-                )}
-                <ListItemText inset={!has}>{key}</ListItemText>
-              </MenuItem>
-            );
-          })}
-        </Menu>
       </Stack>
 
       <Grid container spacing={2}>
@@ -198,6 +101,7 @@ const EnhancedMode = (props: Props) => {
               selected={!!chain.includes(item.uid)}
               itemData={item}
               enableNum={chain.length}
+              logInfo={chainLogs[item.uid]}
               onEnable={() => onEnhanceEnable(item.uid)}
               onDisable={() => onEnhanceDisable(item.uid)}
               onDelete={() => onEnhanceDelete(item.uid)}

+ 9 - 6
src/components/profile/profile-more.tsx

@@ -12,7 +12,6 @@ import {
   Menu,
 } from "@mui/material";
 import { viewProfile } from "@/services/cmds";
-import enhance from "@/services/enhance";
 import ProfileEdit from "./profile-edit";
 import FileEditor from "./file-editor";
 import Notice from "../base/base-notice";
@@ -32,6 +31,7 @@ interface Props {
   selected: boolean;
   itemData: CmdType.ProfileItem;
   enableNum: number;
+  logInfo?: [string, string][];
   onEnable: () => void;
   onDisable: () => void;
   onMoveTop: () => void;
@@ -45,6 +45,7 @@ const ProfileMore = (props: Props) => {
     selected,
     itemData,
     enableNum,
+    logInfo = [],
     onEnable,
     onDisable,
     onMoveTop,
@@ -59,13 +60,13 @@ const ProfileMore = (props: Props) => {
   const [position, setPosition] = useState({ left: 0, top: 0 });
   const [editOpen, setEditOpen] = useState(false);
   const [fileOpen, setFileOpen] = useState(false);
-  const [status, setStatus] = useState(enhance.status(uid));
+  // const [status, setStatus] = useState(enhance.status(uid));
 
   // unlisten when unmount
-  useEffect(() => enhance.listen(uid, setStatus), [uid]);
+  // useEffect(() => enhance.listen(uid, setStatus), [uid]);
 
   // error during enhanced mode
-  const hasError = selected && status?.status === "error";
+  const hasError = !!logInfo.find((e) => e[0] === "exception"); // selected && status?.status === "error";
 
   const onEditInfo = () => {
     setAnchorEl(null);
@@ -188,9 +189,11 @@ const ProfileMore = (props: Props) => {
               noWrap
               color="error"
               sx={{ width: "calc(100% - 75px)" }}
-              title={status.message}
+              // title={status.message}
+              title="error"
             >
-              {status.message}
+              {/* {status.message} */}
+              error
             </Typography>
           ) : (
             <Typography

+ 21 - 28
src/components/setting/mods/clash-field-viewer.tsx

@@ -13,14 +13,18 @@ import {
   Tooltip,
   Typography,
 } from "@mui/material";
-import { BuildCircleRounded, InfoRounded } from "@mui/icons-material";
-import { changeProfileValid, getProfiles } from "@/services/cmds";
+import { InfoRounded } from "@mui/icons-material";
+import {
+  changeProfileValid,
+  getProfiles,
+  getRuntimeExists,
+} from "@/services/cmds";
 import { ModalHandler } from "@/hooks/use-modal-handler";
-import enhance, {
-  DEFAULT_FIELDS,
+import {
   HANDLE_FIELDS,
-  USE_FLAG_FIELDS,
-} from "@/services/enhance";
+  DEFAULT_FIELDS,
+  OTHERS_FIELDS,
+} from "@/utils/clash-fields";
 import Notice from "@/components/base/base-notice";
 
 interface Props {
@@ -36,19 +40,21 @@ const fieldSorter = (a: string, b: string) => {
   return 0;
 };
 
-const useFields = [...USE_FLAG_FIELDS].sort(fieldSorter);
+const otherFields = [...OTHERS_FIELDS].sort(fieldSorter);
 const handleFields = [...HANDLE_FIELDS, ...DEFAULT_FIELDS].sort(fieldSorter);
 
 const ClashFieldViewer = ({ handler }: Props) => {
   const { t } = useTranslation();
 
   const { data, mutate } = useSWR("getProfiles", getProfiles);
+  const { data: existsKeys = [] } = useSWR(
+    "getRuntimeExists",
+    getRuntimeExists
+  );
 
   const [open, setOpen] = useState(false);
   const [selected, setSelected] = useState<string[]>([]);
 
-  const { config: enhanceConfig, use: enhanceUse } = enhance.getFieldsState();
-
   if (handler) {
     handler.current = {
       open: () => setOpen(true),
@@ -61,8 +67,8 @@ const ClashFieldViewer = ({ handler }: Props) => {
   }, [open]);
 
   useEffect(() => {
-    setSelected([...(data?.valid || []), ...enhanceUse]);
-  }, [data?.valid, enhanceUse]);
+    setSelected(data?.valid || []);
+  }, [data?.valid]);
 
   const handleChange = (item: string) => {
     if (!item) return;
@@ -75,7 +81,7 @@ const ClashFieldViewer = ({ handler }: Props) => {
   const handleSave = async () => {
     setOpen(false);
 
-    const oldSet = new Set([...(data?.valid || []), ...enhanceUse]);
+    const oldSet = new Set(data?.valid || []);
     const curSet = new Set(selected);
     const joinSet = new Set(selected.concat([...oldSet]));
 
@@ -103,10 +109,9 @@ const ClashFieldViewer = ({ handler }: Props) => {
           userSelect: "text",
         }}
       >
-        {useFields.map((item) => {
+        {otherFields.map((item) => {
           const inSelect = selected.includes(item);
-          const inConfig = enhanceConfig.includes(item);
-          const inConfigUse = enhanceUse.includes(item);
+          const inConfig = existsKeys.includes(item);
           const inValid = data?.valid?.includes(item);
 
           return (
@@ -119,8 +124,7 @@ const ClashFieldViewer = ({ handler }: Props) => {
               />
               <Typography width="100%">{item}</Typography>
 
-              {inConfigUse && !inValid && <InfoIcon />}
-              {!inSelect && inConfig && <WarnIcon />}
+              {!inSelect && inConfig && !inValid && <WarnIcon />}
             </Stack>
           );
         })}
@@ -159,15 +163,4 @@ function WarnIcon() {
   );
 }
 
-function InfoIcon() {
-  return (
-    <Tooltip title="This field is provided by Merge Profile.">
-      <BuildCircleRounded
-        color="info"
-        sx={{ cursor: "pointer", opacity: 0.5 }}
-      />
-    </Tooltip>
-  );
-}
-
 export default ClashFieldViewer;

+ 4 - 4
src/components/setting/mods/config-viewer.tsx

@@ -10,8 +10,8 @@ import {
   DialogTitle,
 } from "@mui/material";
 import { InfoRounded } from "@mui/icons-material";
-import { atomThemeMode } from "../../../services/states";
-import { getRunningConfig } from "../../../services/cmds";
+import { atomThemeMode } from "@/services/states";
+import { getRuntimeYaml } from "@/services/cmds";
 
 import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js";
 import "monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution.js";
@@ -29,7 +29,7 @@ const ConfigViewer = () => {
   useEffect(() => {
     if (!open) return;
 
-    getRunningConfig().then((data) => {
+    getRuntimeYaml().then((data) => {
       const dom = editorRef.current;
 
       if (!dom) return;
@@ -56,7 +56,7 @@ const ConfigViewer = () => {
     <>
       <Dialog open={open} onClose={() => setOpen(false)}>
         <DialogTitle>
-          {t("Running Config")} <Chip label="ReadOnly" size="small" />
+          {t("Runtime Config")} <Chip label="ReadOnly" size="small" />
         </DialogTitle>
 
         <DialogContent sx={{ width: 520, pb: 1 }}>

+ 0 - 3
src/main.tsx

@@ -7,11 +7,8 @@ import ReactDOM from "react-dom";
 import { RecoilRoot } from "recoil";
 import { BrowserRouter } from "react-router-dom";
 import Layout from "./pages/_layout";
-import enhance from "./services/enhance";
 import "./services/i18n";
 
-enhance.setup();
-
 ReactDOM.render(
   <React.StrictMode>
     <RecoilRoot>

+ 14 - 2
src/services/cmds.ts

@@ -69,8 +69,20 @@ export async function getClashInfo() {
   return invoke<CmdType.ClashInfo | null>("get_clash_info");
 }
 
-export async function getRunningConfig() {
-  return invoke<string | null>("get_running_config");
+export async function getRuntimeConfig() {
+  return invoke<any | null>("get_runtime_config");
+}
+
+export async function getRuntimeYaml() {
+  return invoke<string | null>("get_runtime_yaml");
+}
+
+export async function getRuntimeExists() {
+  return invoke<string[]>("get_runtime_exists");
+}
+
+export async function getRuntimeLogs() {
+  return invoke<Record<string, [string, string][]>>("get_runtime_logs");
 }
 
 export async function patchClashConfig(payload: Partial<ApiType.ConfigData>) {

+ 0 - 259
src/services/enhance.ts

@@ -1,259 +0,0 @@
-import { emit, listen, Event } from "@tauri-apps/api/event";
-import { appWindow } from "@tauri-apps/api/window";
-import ignoreCase from "@/utils/ignore-case";
-
-export const HANDLE_FIELDS = [
-  "port",
-  "mixed-port",
-  "allow-lan",
-  "mode",
-  "log-level",
-  "ipv6",
-  "secret",
-  "external-controller",
-];
-
-export const DEFAULT_FIELDS = [
-  "rules",
-  "proxies",
-  "proxy-groups",
-  "proxy-providers",
-  "rule-providers",
-] as const;
-
-export const USE_FLAG_FIELDS = [
-  "tun",
-  "dns",
-  "ebpf",
-  "hosts",
-  "script",
-  "profile",
-  "payload",
-  "auto-redir",
-  "experimental",
-  "interface-name",
-  "routing-mark",
-  "socks-port",
-  "redir-port",
-  "tproxy-port",
-  "iptables",
-  "external-ui",
-  "bind-address",
-  "authentication",
-  "sniffer", // meta
-  "geodata-mode", // meta
-  "tcp-concurrent", // meta
-] as const;
-
-/**
- * process the merge mode
- */
-function toMerge(merge: CmdType.ProfileMerge, data: CmdType.ProfileData) {
-  if (!merge) return { data, use: [] };
-
-  const {
-    use,
-    "prepend-rules": preRules,
-    "append-rules": postRules,
-    "prepend-proxies": preProxies,
-    "append-proxies": postProxies,
-    "prepend-proxy-groups": preProxyGroups,
-    "append-proxy-groups": postProxyGroups,
-    ...mergeConfig
-  } = merge;
-
-  [...DEFAULT_FIELDS, ...USE_FLAG_FIELDS].forEach((key) => {
-    // the value should not be null
-    if (mergeConfig[key] != null) {
-      data[key] = mergeConfig[key];
-    }
-  });
-
-  // init
-  if (!data.rules) data.rules = [];
-  if (!data.proxies) data.proxies = [];
-  if (!data["proxy-groups"]) data["proxy-groups"] = [];
-
-  // rules
-  if (Array.isArray(preRules)) {
-    data.rules.unshift(...preRules);
-  }
-  if (Array.isArray(postRules)) {
-    data.rules.push(...postRules);
-  }
-
-  // proxies
-  if (Array.isArray(preProxies)) {
-    data.proxies.unshift(...preProxies);
-  }
-  if (Array.isArray(postProxies)) {
-    data.proxies.push(...postProxies);
-  }
-
-  // proxy-groups
-  if (Array.isArray(preProxyGroups)) {
-    data["proxy-groups"].unshift(...preProxyGroups);
-  }
-  if (Array.isArray(postProxyGroups)) {
-    data["proxy-groups"].push(...postProxyGroups);
-  }
-
-  return { data, use: Array.isArray(use) ? use : [] };
-}
-
-/**
- * process the script mode
- */
-function toScript(
-  script: string,
-  data: CmdType.ProfileData
-): Promise<CmdType.ProfileData> {
-  if (!script) {
-    throw new Error("miss the main function");
-  }
-
-  const paramsName = `__verge${Math.floor(Math.random() * 1000)}`;
-  const code = `'use strict';${script};return main(${paramsName});`;
-  const func = new Function(paramsName, code);
-  return func(data);
-}
-
-export type EStatus = { status: "ok" | "error"; message?: string };
-export type EListener = (status: EStatus) => void;
-export type EUnlistener = () => void;
-
-/**
- * The service helps to
- * implement enhanced profiles
- */
-class Enhance {
-  private isSetup = false;
-  private listenMap: Map<string, EListener>;
-  private resultMap: Map<string, EStatus>;
-
-  // record current config fields
-  private fieldsState = {
-    config: [] as string[],
-    use: [] as string[],
-  };
-
-  constructor() {
-    this.listenMap = new Map();
-    this.resultMap = new Map();
-  }
-
-  // setup some listener
-  // for the enhanced running status
-  listen(uid: string, cb: EListener): EUnlistener {
-    this.listenMap.set(uid, cb);
-    return () => this.listenMap.delete(uid);
-  }
-
-  // get the running status
-  status(uid: string): EStatus | undefined {
-    return this.resultMap.get(uid);
-  }
-
-  // get the running field state
-  getFieldsState() {
-    return this.fieldsState;
-  }
-
-  async enhanceHandler(event: Event<unknown>) {
-    const payload = event.payload as CmdType.EnhancedPayload;
-
-    const result = await this.runner(payload).catch((err: any) => ({
-      data: null,
-      status: "error",
-      error: err.message,
-    }));
-
-    emit(payload.callback, JSON.stringify(result)).catch(console.error);
-  }
-
-  // setup the handler
-  setup() {
-    if (this.isSetup) return;
-    this.isSetup = true;
-
-    listen("script-handler", async (event) => {
-      await this.enhanceHandler(event);
-    });
-
-    listen("script-handler-close", async (event) => {
-      await this.enhanceHandler(event);
-      appWindow.close();
-    });
-  }
-
-  // enhanced mode runner
-  private async runner(payload: CmdType.EnhancedPayload) {
-    const chain = payload.chain || [];
-    const valid = payload.valid || [];
-
-    if (!Array.isArray(chain)) throw new Error("unhandle error");
-
-    let pdata = payload.current || {};
-    let useList = valid;
-
-    for (const each of chain) {
-      const { uid, type = "" } = each.item;
-
-      try {
-        // process script
-        if (type === "script") {
-          // support async main function
-          pdata = await toScript(each.script!, ignoreCase(pdata));
-        }
-
-        // process merge
-        else if (type === "merge") {
-          const temp = toMerge(each.merge!, ignoreCase(pdata));
-          pdata = temp.data;
-          useList = useList.concat(temp.use || []);
-        }
-
-        // invalid type
-        else {
-          throw new Error(`invalid enhanced profile type "${type}"`);
-        }
-
-        this.exec(uid, { status: "ok" });
-      } catch (err: any) {
-        console.error(err);
-
-        this.exec(uid, {
-          status: "error",
-          message: err.message || err.toString(),
-        });
-      }
-    }
-
-    pdata = ignoreCase(pdata);
-
-    // save the fields state
-    this.fieldsState.config = Object.keys(pdata);
-    this.fieldsState.use = [...useList];
-
-    // filter the data
-    const filterData: typeof pdata = {};
-    Object.keys(pdata).forEach((key: any) => {
-      if (
-        DEFAULT_FIELDS.includes(key) ||
-        (USE_FLAG_FIELDS.includes(key) && useList.includes(key))
-      ) {
-        filterData[key] = pdata[key];
-      }
-    });
-
-    return { data: filterData, status: "ok" };
-  }
-
-  // exec the listener
-  private exec(uid: string, status: EStatus) {
-    this.resultMap.set(uid, status);
-    this.listenMap.get(uid)?.(status);
-  }
-}
-
-export default new Enhance();

+ 42 - 0
src/utils/clash-fields.ts

@@ -0,0 +1,42 @@
+export const HANDLE_FIELDS = [
+  "port",
+  "socks-port",
+  "mixed-port",
+  "allow-lan",
+  "mode",
+  "log-level",
+  "ipv6",
+  "secret",
+  "external-controller",
+];
+
+export const DEFAULT_FIELDS = [
+  "rules",
+  "proxies",
+  "proxy-groups",
+  "proxy-providers",
+  "rule-providers",
+] as const;
+
+export const OTHERS_FIELDS = [
+  "tun",
+  "dns",
+  "ebpf",
+  "hosts",
+  "script",
+  "profile",
+  "payload",
+  "auto-redir",
+  "experimental",
+  "interface-name",
+  "routing-mark",
+  "redir-port",
+  "tproxy-port",
+  "iptables",
+  "external-ui",
+  "bind-address",
+  "authentication",
+  "sniffer", // meta
+  "geodata-mode", // meta
+  "tcp-concurrent", // meta
+] as const;