fix: zapret2 runtime resolution, tailscale proxy installation and protocol labels in server lists

This commit is contained in:
Dushnilin
2026-09-08 21:57:42 +03:00
parent 625fa1e9b3
commit f92c931a72
6 changed files with 392 additions and 80 deletions
@@ -1062,6 +1062,54 @@ function getProxyUrlName(url) {
}
}
// src/helpers/formatOutboundType.ts
function formatOutboundType(type, transport) {
const cleanType = (type || "").trim();
const cleanTransport = (transport || "").trim().toUpperCase();
if (!cleanType) {
return cleanTransport ? `(${cleanTransport})` : "";
}
if (!cleanTransport) {
return cleanType;
}
if (cleanType.toUpperCase().includes(`(${cleanTransport})`)) {
return cleanType;
}
return `${cleanType} (${cleanTransport})`;
}
// src/helpers/getProxyUrlTransport.ts
function getProxyUrlTransport(url) {
if (!url || typeof url !== "string") {
return void 0;
}
try {
const trimmed = url.trim();
if (trimmed.startsWith("vmess://")) {
const b64 = trimmed.slice(8).split("#")[0];
if (typeof atob === "function") {
const jsonStr = atob(b64.replace(/[\r\n]/g, ""));
const obj = JSON.parse(jsonStr);
if (obj && typeof obj.net === "string" && obj.net.trim()) {
return obj.net.trim().toLowerCase();
}
}
}
const [base] = trimmed.split("#");
const queryIdx = base.indexOf("?");
if (queryIdx !== -1) {
const query = base.slice(queryIdx + 1);
const params = new URLSearchParams(query);
const transport = params.get("type");
if (transport && transport.trim()) {
return transport.trim().toLowerCase();
}
}
} catch {
}
return void 0;
}
// src/tachyon/tabs/dashboard/partials/renderFlagEmojis.ts
var FLAG_EMOJI_PATTERN = /([\u{1f1e6}-\u{1f1ff}]{2}|\u{1f3f4}[\u{e0061}-\u{e007a}]+\u{e007f})/gu;
var EXACT_FLAG_EMOJI_PATTERN = /^([\u{1f1e6}-\u{1f1ff}]{2}|\u{1f3f4}[\u{e0061}-\u{e007a}]+\u{e007f})$/u;
@@ -1423,54 +1471,6 @@ function isCopyableProxyLink(link) {
return COPYABLE_PROXY_URI_RE.test((link || "").trim());
}
// src/helpers/formatOutboundType.ts
function formatOutboundType(type, transport) {
const cleanType = (type || "").trim();
const cleanTransport = (transport || "").trim().toUpperCase();
if (!cleanType) {
return cleanTransport ? `(${cleanTransport})` : "";
}
if (!cleanTransport) {
return cleanType;
}
if (cleanType.toUpperCase().includes(`(${cleanTransport})`)) {
return cleanType;
}
return `${cleanType} (${cleanTransport})`;
}
// src/helpers/getProxyUrlTransport.ts
function getProxyUrlTransport(url) {
if (!url || typeof url !== "string") {
return void 0;
}
try {
const trimmed = url.trim();
if (trimmed.startsWith("vmess://")) {
const b64 = trimmed.slice(8).split("#")[0];
if (typeof atob === "function") {
const jsonStr = atob(b64.replace(/[\r\n]/g, ""));
const obj = JSON.parse(jsonStr);
if (obj && typeof obj.net === "string" && obj.net.trim()) {
return obj.net.trim().toLowerCase();
}
}
}
const [base] = trimmed.split("#");
const queryIdx = base.indexOf("?");
if (queryIdx !== -1) {
const query = base.slice(queryIdx + 1);
const params = new URLSearchParams(query);
const transport = params.get("type");
if (transport && transport.trim()) {
return transport.trim().toLowerCase();
}
}
} catch {
}
return void 0;
}
// src/icons/renderLoaderCircleIcon24.ts
function renderLoaderCircleIcon24() {
const NS = "http://www.w3.org/2000/svg";
@@ -22002,8 +22002,10 @@ return baseclass.extend({
applyUiStateToStore,
bulkValidate,
coreService,
formatOutboundType,
getClashUIUrl,
getProxyUrlName,
getProxyUrlTransport,
injectGlobalStyles,
parseValueList,
renderDnsBenchmarkModal,
@@ -494,12 +494,147 @@ let actionProvidersAvailabilityPromise = null;
let actionProvidersAvailabilityLoader = null;
const outboundNameChoicesCache = new Map();
const outboundNameChoicesInflight = new Map();
const outboundTypeByNameCache = new Map();
const outboundNameSourceOptions = new Map();
const sectionGroupSourceOptions = new Map();
const dashboardFilterChoiceRefreshers = new Map();
const perRuleDnsTypeState = new Map();
const perRuleDnsWidgets = new Map();
const SECTION_CACHE_DIR = "/var/run/tachyon/section-cache";
function normalizeProtocolName(protocol) {
if (!protocol) {
return "";
}
const p = `${protocol}`.trim().toLowerCase();
switch (p) {
case "vless":
return "VLESS";
case "vmess":
return "VMess";
case "shadowsocks":
case "ss":
return "Shadowsocks";
case "trojan":
return "Trojan";
case "wireguard":
case "wg":
return "WireGuard";
case "hysteria2":
case "hy2":
return "Hysteria2";
case "hysteria":
return "Hysteria";
case "tuic":
return "TUIC";
case "socks":
case "socks5":
return "SOCKS5";
case "http":
return "HTTP";
case "direct":
return "Direct";
case "block":
return "Block";
default:
return p.charAt(0).toUpperCase() + p.slice(1);
}
}
function normalizeTransportName(transport) {
if (!transport) {
return "";
}
const t = `${transport}`.trim().toLowerCase();
switch (t) {
case "xhttp":
return "XHTTP";
case "ws":
case "websocket":
return "WS";
case "grpc":
return "gRPC";
case "http":
case "h2":
return "HTTP";
case "tcp":
case "raw":
return "TCP";
case "quic":
return "QUIC";
case "upgrade":
case "httpupgrade":
return "HTTPUpgrade";
default:
return t.toUpperCase();
}
}
function formatOutboundTypeLabel(protocol, transport) {
const normProto = normalizeProtocolName(protocol);
const normTrans = normalizeTransportName(transport);
if (!normProto && !normTrans) {
return "";
}
if (!normProto) {
return normTrans;
}
if (!normTrans) {
return normProto;
}
if (normProto.toUpperCase().includes(normTrans.toUpperCase())) {
return normProto;
}
if (["WireGuard", "Hysteria", "Hysteria2", "TUIC"].includes(normProto)) {
return normProto;
}
return `${normProto} (${normTrans})`;
}
function getProxyUrlTypeLabel(url) {
if (!url || typeof url !== "string") {
return "";
}
const trimmed = url.trim();
const schemeMatch = trimmed.match(/^([a-zA-Z0-9_-]+):\/\//);
if (!schemeMatch) {
return "";
}
const scheme = schemeMatch[1].toLowerCase();
let proto = scheme;
let transport = "";
if (scheme === "vmess") {
proto = "VMess";
try {
const b64 = trimmed.slice(8).split("#")[0];
if (typeof atob === "function") {
const jsonStr = atob(b64.replace(/[\r\n]/g, ""));
const obj = JSON.parse(jsonStr);
if (obj && typeof obj.net === "string") {
transport = obj.net;
}
}
} catch (_e) {}
} else {
try {
const [base] = trimmed.split("#");
const queryIdx = base.indexOf("?");
if (queryIdx !== -1) {
const query = base.slice(queryIdx + 1);
const params = new URLSearchParams(query);
const t = params.get("type");
if (t && t.trim()) {
transport = t.trim();
}
}
} catch (_e) {}
}
return formatOutboundTypeLabel(proto, transport);
}
const COUNTRY_CODES =
"AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS YE YT ZA ZM ZW XK".split(
" ",
@@ -672,6 +807,8 @@ function filteredOutboundMetadataFromCache(cache) {
const metadata = plainObject(plainObject(cache).outboundMetadata);
const names = plainObject(metadata.names);
const countries = plainObject(metadata.countries);
const protocols = plainObject(metadata.protocols);
const transports = plainObject(metadata.transports);
const candidateTags = Array.isArray(cache.urltestCandidateTags)
? cache.urltestCandidateTags
: [];
@@ -679,6 +816,8 @@ function filteredOutboundMetadataFromCache(cache) {
const result = {
names: {},
countries: {},
protocols: {},
transports: {},
};
if (candidateTags.length > 0) {
@@ -693,6 +832,12 @@ function filteredOutboundMetadataFromCache(cache) {
if (countries[tag] != null) {
result.countries[tag] = countries[tag];
}
if (protocols[tag] != null) {
result.protocols[tag] = protocols[tag];
}
if (transports[tag] != null) {
result.transports[tag] = transports[tag];
}
});
return result;
}
@@ -707,6 +852,16 @@ function filteredOutboundMetadataFromCache(cache) {
result.countries[tag] = country;
}
});
Object.entries(protocols).forEach(([tag, protocol]) => {
if (!groups[tag]) {
result.protocols[tag] = protocol;
}
});
Object.entries(transports).forEach(([tag, transport]) => {
if (!groups[tag]) {
result.transports[tag] = transport;
}
});
return result;
}
@@ -714,13 +869,23 @@ function filteredOutboundMetadataFromCache(cache) {
function readOutboundMetadataFromSectionCache(section_id) {
const targetId = section_id || "Main";
if (!safeCacheSectionName(targetId)) {
return Promise.resolve({ names: {}, countries: {} });
return Promise.resolve({
names: {},
countries: {},
protocols: {},
transports: {},
});
}
return fs
.read(`${SECTION_CACHE_DIR}/${targetId}.json`)
.then((raw) => filteredOutboundMetadataFromCache(JSON.parse(raw || "{}")))
.catch(() => ({ names: {}, countries: {} }));
.catch(() => ({
names: {},
countries: {},
protocols: {},
transports: {},
}));
}
function loadOutboundNameChoices(section_id) {
@@ -737,6 +902,25 @@ function loadOutboundNameChoices(section_id) {
.then((metadata) => {
const names = Object.values(plainObject(metadata.names));
const typeMap = outboundTypeByNameCache.get(targetId) || new Map();
Object.entries(plainObject(metadata.names)).forEach(([tag, name]) => {
if (!name) {
return;
}
const proto = metadata.protocols ? metadata.protocols[tag] : "";
const trans = metadata.transports ? metadata.transports[tag] : "";
const label = formatOutboundTypeLabel(proto, trans);
if (label) {
typeMap.set(name, label);
}
});
outboundTypeByNameCache.set(targetId, typeMap);
if (targetId !== "Main") {
const mainTypeMap = outboundTypeByNameCache.get("Main") || new Map();
typeMap.forEach((v, k) => mainTypeMap.set(k, v));
outboundTypeByNameCache.set("Main", mainTypeMap);
}
const choices = names
.filter(Boolean)
.filter((name, index, values) => values.indexOf(name) === index)
@@ -1620,6 +1804,61 @@ function countryChoices() {
})).sort((a, b) => a.label.localeCompare(b.label));
}
function getOutboundTypeForName(section_id, name) {
if (!name) {
return "";
}
const targetId = section_id || "Main";
const typeMap = outboundTypeByNameCache.get(targetId);
if (typeMap && typeMap.has(name)) {
return typeMap.get(name);
}
if (targetId !== "Main") {
const mainMap = outboundTypeByNameCache.get("Main");
if (mainMap && mainMap.has(name)) {
return mainMap.get(name);
}
}
for (const map of outboundTypeByNameCache.values()) {
if (map && typeof map.get === "function" && map.has(name)) {
return map.get(name);
}
}
let found = "";
currentSourceOptionValues(section_id, "selector_proxy_links").forEach(
(link) => {
if (found) {
return;
}
const linkName = main.getProxyUrlName(`${link || ""}`);
if (linkName === name) {
found = getProxyUrlTypeLabel(link);
}
},
);
if (found) {
return found;
}
currentSourceOptionValues(section_id, "outbound_jsons").forEach((jsonStr) => {
if (found) {
return;
}
const parsed = parseOutboundJsonObject(jsonStr);
if (parsed && (parsed.tag || "") === name) {
const proto = parsed.type || "";
const trans = (parsed.transport && parsed.transport.type) || "";
found = formatOutboundTypeLabel(proto, trans);
}
});
return found;
}
function currentOutboundNameChoices(section_id, values) {
if (!outboundNameChoicesCache.has(section_id)) {
loadOutboundNameChoices(section_id);
@@ -1634,7 +1873,12 @@ function currentOutboundNameChoices(section_id, values) {
}
seen.add(value);
result.push({ value, label: value });
const typeStr = getOutboundTypeForName(section_id, value);
const label =
typeStr && !value.endsWith(`[${typeStr}]`)
? `${value} [${typeStr}]`
: value;
result.push({ value, label });
};
(outboundNameChoicesCache.get(section_id) || []).forEach(append);