From c9649ac50156e0a432a1b2e94d16662ffc0bec19 Mon Sep 17 00:00:00 2001 From: GyDi Date: Sun, 27 Mar 2022 20:36:13 +0800 Subject: [PATCH] feat: enhanced mode supports more fields --- src/services/enhance.ts | 203 +++++++++++++++++++++++---------------- src/services/types.ts | 30 +++++- src/utils/ignore-case.ts | 14 +++ 3 files changed, 163 insertions(+), 84 deletions(-) create mode 100644 src/utils/ignore-case.ts diff --git a/src/services/enhance.ts b/src/services/enhance.ts index 57e4ff6..ee746d3 100644 --- a/src/services/enhance.ts +++ b/src/services/enhance.ts @@ -1,48 +1,80 @@ import { emit, listen } from "@tauri-apps/api/event"; import { CmdType } from "./types"; +import ignoreCase from "../utils/ignore-case"; + +const DEFAULT_FIELDS = [ + "rules", + "proxies", + "proxy-groups", + "proxy-providers", + "rule-providers", +] as const; + +const USE_FLAG_FIELDS = [ + "tun", + "dns", + "hosts", + "script", + "profile", + "payload", + "interface-name", + "routing-mark", +] as const; /** * process the merge mode */ -function toMerge( - merge: CmdType.ProfileMerge, - data: CmdType.ProfileData -): CmdType.ProfileData { - if (!merge) return data; +function toMerge(merge: CmdType.ProfileMerge, data: CmdType.ProfileData) { + if (!merge) return { data, use: [] }; - const newData = { ...data }; + 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(merge["prepend-rules"])) { - if (!newData.rules) newData.rules = []; - newData.rules.unshift(...merge["prepend-rules"]); + if (Array.isArray(preRules)) { + data.rules.unshift(...preRules); } - if (Array.isArray(merge["append-rules"])) { - if (!newData.rules) newData.rules = []; - newData.rules.push(...merge["append-rules"]); + if (Array.isArray(postRules)) { + data.rules.push(...postRules); } // proxies - if (Array.isArray(merge["prepend-proxies"])) { - if (!newData.proxies) newData.proxies = []; - newData.proxies.unshift(...merge["prepend-proxies"]); + if (Array.isArray(preProxies)) { + data.proxies.unshift(...preProxies); } - if (Array.isArray(merge["append-proxies"])) { - if (!newData.proxies) newData.proxies = []; - newData.proxies.push(...merge["append-proxies"]); + if (Array.isArray(postProxies)) { + data.proxies.push(...postProxies); } // proxy-groups - if (Array.isArray(merge["prepend-proxy-groups"])) { - if (!newData["proxy-groups"]) newData["proxy-groups"] = []; - newData["proxy-groups"].unshift(...merge["prepend-proxy-groups"]); + if (Array.isArray(preProxyGroups)) { + data["proxy-groups"].unshift(...preProxyGroups); } - if (Array.isArray(merge["append-proxy-groups"])) { - if (!newData["proxy-groups"]) newData["proxy-groups"] = []; - newData["proxy-groups"].push(...merge["append-proxy-groups"]); + if (Array.isArray(postProxyGroups)) { + data["proxy-groups"].push(...postProxyGroups); } - return newData; + return { data, use: Array.isArray(use) ? use : [] }; } /** @@ -99,70 +131,75 @@ class Enhance { listen("script-handler", async (event) => { const payload = event.payload as CmdType.EnhancedPayload; - let pdata = payload.current || {}; - let hasScript = false; + const result = await this.runner(payload).catch((err: any) => ({ + data: null, + status: "error", + error: err.message, + })); - for (const each of payload.chain) { - const { uid, type = "" } = each.item; - - try { - // process script - if (type === "script") { - // support async main function - pdata = await toScript(each.script!, { ...pdata }); - hasScript = true; - } - - // process merge - else if (type === "merge") { - pdata = toMerge(each.merge!, { ...pdata }); - } - - // invalid type - else { - throw new Error(`invalid enhanced profile type "${type}"`); - } - - this.exec(uid, { status: "ok" }); - } catch (err: any) { - this.exec(uid, { - status: "error", - message: err.message || err.toString(), - }); - - console.error(err); - } - } - - // If script is never used - // filter other fields - if (!hasScript) { - const validKeys = [ - "proxies", - "proxy-providers", - "proxy-groups", - "rule-providers", - "rules", - ]; - - // to lowercase - const newData: any = {}; - Object.keys(pdata).forEach((key) => { - const newKey = key.toLowerCase(); - if (validKeys.includes(newKey)) { - newData[newKey] = (pdata as any)[key]; - } - }); - - pdata = newData; - } - - const result = { data: pdata, status: "ok" }; emit(payload.callback, JSON.stringify(result)).catch(console.error); }); } + // enhanced mode runner + private async runner(payload: CmdType.EnhancedPayload) { + const chain = payload.chain || []; + + if (!Array.isArray(chain)) throw new Error("unhandle error"); + + let pdata = payload.current || {}; + let useList = [] as string[]; + + 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); + + // 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); diff --git a/src/services/types.ts b/src/services/types.ts index 18727e1..37e2e14 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -132,7 +132,33 @@ export namespace CmdType { system_proxy_bypass?: string; } - export type ProfileMerge = Record; + type ClashConfigValue = any; + + export interface ProfileMerge { + // clash config fields (default supports) + rules?: ClashConfigValue; + proxies?: ClashConfigValue; + "proxy-groups"?: ClashConfigValue; + "proxy-providers"?: ClashConfigValue; + "rule-providers"?: ClashConfigValue; + // clash config fields (use flag) + tun?: ClashConfigValue; + dns?: ClashConfigValue; + hosts?: ClashConfigValue; + script?: ClashConfigValue; + profile?: ClashConfigValue; + payload?: ClashConfigValue; + "interface-name"?: ClashConfigValue; + "routing-mark"?: ClashConfigValue; + // functional fields + use?: string[]; + "prepend-rules"?: any[]; + "append-rules"?: any[]; + "prepend-proxies"?: any[]; + "append-proxies"?: any[]; + "prepend-proxy-groups"?: any[]; + "append-proxy-groups"?: any[]; + } // partial of the clash config export type ProfileData = Partial<{ @@ -141,6 +167,8 @@ export namespace CmdType { "proxy-groups": any[]; "proxy-providers": any[]; "rule-providers": any[]; + + [k: string]: any; }>; export interface ChainItem { diff --git a/src/utils/ignore-case.ts b/src/utils/ignore-case.ts new file mode 100644 index 0000000..d817d4e --- /dev/null +++ b/src/utils/ignore-case.ts @@ -0,0 +1,14 @@ +// Shallow copy and change all keys to lowercase +type TData = Record; + +export default function ignoreCase(data: TData): TData { + if (!data) return data; + + const newData = {} as TData; + + Object.keys(data).forEach((key) => { + newData[key.toLowerCase()] = data[key]; + }); + + return newData; +}