Show copy button only for proxy keys

This commit is contained in:
ushan0v
2026-05-14 03:18:48 +03:00
parent 580b1bb644
commit e59a6355fd
8 changed files with 605 additions and 425 deletions
+1
View File
@@ -11,3 +11,4 @@ export * from './preserveScrollForPage';
export * from './parseQueryString';
export * from './svgEl';
export * from './insertIf';
export * from './isCopyableProxyLink';
+3 -1
View File
@@ -3,9 +3,11 @@ type HtmlTag = keyof HTMLElementTagNameMap;
type HtmlElement<T extends HtmlTag> = HTMLElementTagNameMap[T];
type HtmlAttributes<T extends HtmlTag = 'div'> = Partial<
Omit<HtmlElement<T>, 'style' | 'children'> & {
Omit<HtmlElement<T>, 'style' | 'children' | 'click'> & {
style?: string | Partial<CSSStyleDeclaration>;
class?: string;
'aria-label'?: string;
click?: (event: MouseEvent) => void;
onclick?: (event: MouseEvent) => void;
}
>;
@@ -1,6 +1,11 @@
import { getConfigSections } from './getConfigSections';
import { Podkop } from '../../types';
import { getProxyUrlName, splitProxyString } from '../../../helpers';
import {
getProxyUrlName,
isCopyableProxyLink,
isCopyableProxyOutboundType,
splitProxyString,
} from '../../../helpers';
import { PodkopShellMethods } from '../shell';
interface IGetDashboardSectionsResponse {
@@ -50,6 +55,58 @@ function getSectionProxyConfigType(section: Podkop.ConfigSection) {
return undefined;
}
const SUBSCRIPTION_LINK_CACHE_TTL_MS = 60 * 1000;
const subscriptionLinkCache = new Map<
string,
{ canCopyLink: boolean; expiresAt: number }
>();
async function getSubscriptionOutboundCopyState(
sectionName: string,
outbound: Podkop.Outbound,
) {
if (!outbound.code || !isCopyableProxyOutboundType(outbound.type)) {
return false;
}
const cacheKey = `${sectionName}:${outbound.code}`;
const cached = subscriptionLinkCache.get(cacheKey);
const now = Date.now();
if (cached && cached.expiresAt > now) {
return cached.canCopyLink;
}
const response = await PodkopShellMethods.getOutboundLink(
sectionName,
outbound.code,
);
const canCopyLink =
response.success && isCopyableProxyLink(response.data.link);
subscriptionLinkCache.set(cacheKey, {
canCopyLink,
expiresAt: now + SUBSCRIPTION_LINK_CACHE_TTL_MS,
});
return canCopyLink;
}
async function markSubscriptionCopyableOutbounds(
sectionName: string,
outbounds: Podkop.Outbound[],
) {
return Promise.all(
outbounds.map(async (outbound) => ({
...outbound,
canCopyLink: await getSubscriptionOutboundCopyState(
sectionName,
outbound,
),
})),
);
}
export async function getDashboardSections(): Promise<IGetDashboardSectionsResponse> {
const configSections = await getConfigSections();
const clashProxies = await PodkopShellMethods.getClashApiProxies();
@@ -67,222 +124,164 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
value,
}),
);
const data = configSections
.filter(
(section) =>
section.enabled !== '0' &&
['proxy', 'vpn'].includes(getSectionAction(section)),
)
.map((section) => {
const displayName = getDisplayName(section);
const sectionAction = getSectionAction(section);
const proxyConfigType = getSectionProxyConfigType(section);
const data = await Promise.all(
configSections
.filter(
(section) =>
section.enabled !== '0' &&
['proxy', 'vpn'].includes(getSectionAction(section)),
)
.map(async (section) => {
const displayName = getDisplayName(section);
const sectionAction = getSectionAction(section);
const proxyConfigType = getSectionProxyConfigType(section);
if (sectionAction === 'vpn') {
const outbound = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
if (sectionAction === 'vpn') {
const outbound = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
return {
withTagSelect: false,
code: outbound?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds: [
{
code: outbound?.code || section['.name'],
displayName: section.interface || outbound?.value?.name || '',
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || '',
selected: true,
},
],
};
}
if (proxyConfigType === 'url') {
const outbound = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
const activeConfigs = splitProxyString(section.proxy_string || '');
const proxyDisplayName =
getProxyUrlName(activeConfigs?.[0]) || outbound?.value?.name || '';
return {
withTagSelect: false,
code: outbound?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds: [
{
code: outbound?.code || section['.name'],
displayName: proxyDisplayName,
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || '',
selected: true,
link: activeConfigs?.[0] || '',
},
],
};
}
if (proxyConfigType === 'outbound') {
const outbound = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
let parsedTag = '';
try {
const parsedOutbound = JSON.parse(section.outbound_json || '{}');
parsedTag = parsedOutbound?.tag
? decodeURIComponent(parsedOutbound.tag)
: '';
} catch (_error) {
parsedTag = '';
return {
withTagSelect: false,
code: outbound?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds: [
{
code: outbound?.code || section['.name'],
displayName: section.interface || outbound?.value?.name || '',
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || '',
selected: true,
canCopyLink: false,
},
],
};
}
return {
withTagSelect: false,
code: outbound?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds: [
{
code: outbound?.code || section['.name'],
displayName: parsedTag || outbound?.value?.name || '',
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || '',
selected: true,
},
],
};
}
if (proxyConfigType === 'url') {
const outbound = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
if (proxyConfigType === 'selector') {
const selector = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
const activeConfigs = splitProxyString(section.proxy_string || '');
const link = activeConfigs?.[0] || '';
const proxyDisplayName =
getProxyUrlName(link) || outbound?.value?.name || '';
const links = section.selector_proxy_links ?? [];
const outbounds = links
.map((link, index) => ({
link,
outbound: proxies.find(
(item) => item.code === `${section['.name']}-${index + 1}-out`,
),
}))
.map((item) => ({
code: item?.outbound?.code || '',
displayName:
getProxyUrlName(item.link) || item?.outbound?.value?.name || '',
latency: item?.outbound?.value?.history?.[0]?.delay || 0,
type: item?.outbound?.value?.type || '',
selected: selector?.value?.now === item?.outbound?.code,
link: item.link,
}));
return {
withTagSelect: true,
code: selector?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds,
};
}
if (proxyConfigType === 'urltest') {
const selector = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
const outbound = proxies.find(
(proxy) => proxy.code === `${section['.name']}-urltest-out`,
);
const outbounds = (outbound?.value?.all ?? [])
.map((code) => proxies.find((item) => item.code === code))
.map((item, index) => ({
code: item?.code || '',
displayName:
getProxyUrlName(section.urltest_proxy_links?.[index] || '') ||
item?.value?.name ||
'',
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || '',
selected: selector?.value?.now === item?.code,
link: section.urltest_proxy_links?.[index] || '',
}));
return {
withTagSelect: true,
code: selector?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds: [
{
code: outbound?.code || '',
displayName: _('Fastest'),
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || '',
selected: selector?.value?.now === outbound?.code,
},
...outbounds,
],
};
}
if (proxyConfigType === 'subscription') {
const selector = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
const fallbackUrltest = proxies.find(
(proxy) => proxy.code === `${section['.name']}-urltest-out`,
);
const selectorOutbounds = (selector?.value?.all ?? []).flatMap(
(code) => {
const item = proxies.find((proxy) => proxy.code === code);
if (!item) {
return [];
}
const isDefaultFastest =
item.code === `${section['.name']}-urltest-out`;
return [
return {
withTagSelect: false,
code: outbound?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds: [
{
code: item.code,
displayName: isDefaultFastest
? _('Fastest')
: item?.value?.name || '',
code: outbound?.code || section['.name'],
displayName: proxyDisplayName,
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || '',
selected: true,
link,
canCopyLink: isCopyableProxyLink(link),
},
],
};
}
if (proxyConfigType === 'outbound') {
const outbound = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
let parsedTag = '';
try {
const parsedOutbound = JSON.parse(section.outbound_json || '{}');
parsedTag = parsedOutbound?.tag
? decodeURIComponent(parsedOutbound.tag)
: '';
} catch (_error) {
parsedTag = '';
}
return {
withTagSelect: false,
code: outbound?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds: [
{
code: outbound?.code || section['.name'],
displayName: parsedTag || outbound?.value?.name || '',
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || '',
selected: true,
canCopyLink: false,
},
],
};
}
if (proxyConfigType === 'selector') {
const selector = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
const links = section.selector_proxy_links ?? [];
const outbounds = links
.map((link, index) => ({
link,
outbound: proxies.find(
(item) => item.code === `${section['.name']}-${index + 1}-out`,
),
}))
.map((item) => {
const link = item.link;
return {
code: item?.outbound?.code || '',
displayName:
getProxyUrlName(link) || item?.outbound?.value?.name || '',
latency: item?.outbound?.value?.history?.[0]?.delay || 0,
type: item?.outbound?.value?.type || '',
selected: selector?.value?.now === item?.outbound?.code,
link,
canCopyLink: isCopyableProxyLink(link),
};
});
return {
withTagSelect: true,
code: selector?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds,
};
}
if (proxyConfigType === 'urltest') {
const selector = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
const outbound = proxies.find(
(proxy) => proxy.code === `${section['.name']}-urltest-out`,
);
const outbounds = (outbound?.value?.all ?? [])
.map((code) => proxies.find((item) => item.code === code))
.map((item, index) => {
const link = section.urltest_proxy_links?.[index] || '';
return {
code: item?.code || '',
displayName: getProxyUrlName(link) || item?.value?.name || '',
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || '',
selected: selector?.value?.now === item.code,
},
];
},
);
const outbounds = [
...selectorOutbounds.filter(
(item) => item.type?.toLowerCase() === 'urltest',
),
...selectorOutbounds.filter(
(item) => item.type?.toLowerCase() !== 'urltest',
),
];
if (outbounds.length === 0 && fallbackUrltest) {
const fallbackOutbounds = (fallbackUrltest?.value?.all ?? [])
.map((code) => proxies.find((item) => item.code === code))
.map((item) => ({
code: item?.code || '',
displayName: item?.value?.name || '',
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || '',
selected: selector?.value?.now === item?.code,
}));
selected: selector?.value?.now === item?.code,
link,
canCopyLink: isCopyableProxyLink(link),
};
});
return {
withTagSelect: true,
@@ -291,34 +290,115 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
displayName,
outbounds: [
{
code: fallbackUrltest?.code || '',
code: outbound?.code || '',
displayName: _('Fastest'),
latency: fallbackUrltest?.value?.history?.[0]?.delay || 0,
type: fallbackUrltest?.value?.type || '',
selected: selector?.value?.now === fallbackUrltest?.code,
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || '',
selected: selector?.value?.now === outbound?.code,
canCopyLink: false,
},
...fallbackOutbounds,
...outbounds,
],
};
}
if (proxyConfigType === 'subscription') {
const selector = proxies.find(
(proxy) => proxy.code === `${section['.name']}-out`,
);
const fallbackUrltest = proxies.find(
(proxy) => proxy.code === `${section['.name']}-urltest-out`,
);
const selectorOutbounds = (selector?.value?.all ?? []).flatMap(
(code) => {
const item = proxies.find((proxy) => proxy.code === code);
if (!item) {
return [];
}
const isDefaultFastest =
item.code === `${section['.name']}-urltest-out`;
return [
{
code: item.code,
displayName: isDefaultFastest
? _('Fastest')
: item?.value?.name || '',
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || '',
selected: selector?.value?.now === item.code,
canCopyLink: false,
},
];
},
);
const outbounds = [
...selectorOutbounds.filter(
(item) => item.type?.toLowerCase() === 'urltest',
),
...selectorOutbounds.filter(
(item) => item.type?.toLowerCase() !== 'urltest',
),
];
if (outbounds.length === 0 && fallbackUrltest) {
const fallbackOutbounds = (fallbackUrltest?.value?.all ?? [])
.map((code) => proxies.find((item) => item.code === code))
.map((item) => ({
code: item?.code || '',
displayName: item?.value?.name || '',
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || '',
selected: selector?.value?.now === item?.code,
canCopyLink: false,
}));
return {
withTagSelect: true,
code: selector?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds: [
{
code: fallbackUrltest?.code || '',
displayName: _('Fastest'),
latency: fallbackUrltest?.value?.history?.[0]?.delay || 0,
type: fallbackUrltest?.value?.type || '',
selected: selector?.value?.now === fallbackUrltest?.code,
canCopyLink: false,
},
...(await markSubscriptionCopyableOutbounds(
section['.name'],
fallbackOutbounds,
)),
],
};
}
return {
withTagSelect: true,
code: selector?.code || section['.name'],
sectionName: section['.name'],
displayName,
outbounds: await markSubscriptionCopyableOutbounds(
section['.name'],
outbounds,
),
};
}
return {
withTagSelect: true,
code: selector?.code || section['.name'],
withTagSelect: false,
code: section['.name'],
sectionName: section['.name'],
displayName,
outbounds,
outbounds: [],
};
}
return {
withTagSelect: false,
code: section['.name'],
sectionName: section['.name'],
displayName,
outbounds: [],
};
});
}),
);
return {
success: true,
@@ -1,5 +1,6 @@
import {
getClashWsUrl,
isCopyableProxyLink,
onMount,
preserveScrollForPage,
} from '../../../helpers';
@@ -184,8 +185,10 @@ async function handleCopyOutbound(
section: Podkop.OutboundGroup,
outbound: Podkop.Outbound,
) {
if (outbound.link) {
copyToClipboard(outbound.link);
const link = outbound.link;
if (link && isCopyableProxyLink(link)) {
copyToClipboard(link);
return;
}
@@ -194,7 +197,7 @@ async function handleCopyOutbound(
outbound.code,
);
if (response.success && response.data.link) {
if (response.success && isCopyableProxyLink(response.data.link)) {
copyToClipboard(response.data.link);
return;
}
@@ -215,6 +218,7 @@ async function renderSectionsWidget() {
failed: sectionsWidget.failed,
section: {
code: '',
sectionName: '',
displayName: '',
outbounds: [],
withTagSelect: false,
@@ -1,4 +1,5 @@
import { renderCopyIcon24 } from '../../../../icons';
import { isCopyableProxyLink } from '../../../../helpers';
import { Podkop } from '../../../types';
interface IRenderSectionsProps {
@@ -68,8 +69,7 @@ export function renderDefaultState({
}
const canCopyLink =
outbound.type?.toLowerCase() !== 'urltest' &&
Boolean(outbound.code || outbound.link);
Boolean(outbound.canCopyLink) || isCopyableProxyLink(outbound.link);
return E(
'div',
@@ -40,12 +40,14 @@ export function render() {
failed: false,
section: {
code: '',
sectionName: '',
displayName: '',
outbounds: [],
withTagSelect: false,
},
onTestLatency: () => {},
onChooseOutbound: () => {},
onCopyOutbound: () => {},
latencyFetching: false,
}),
),
+1
View File
@@ -87,6 +87,7 @@ export namespace Podkop {
type: string;
selected: boolean;
link?: string;
canCopyLink?: boolean;
}
export interface OutboundGroup {
@@ -869,6 +869,40 @@ function getSectionProxyConfigType(section) {
}
return void 0;
}
var SUBSCRIPTION_LINK_CACHE_TTL_MS = 60 * 1e3;
var subscriptionLinkCache = /* @__PURE__ */ new Map();
async function getSubscriptionOutboundCopyState(sectionName, outbound) {
if (!outbound.code || !isCopyableProxyOutboundType(outbound.type)) {
return false;
}
const cacheKey = `${sectionName}:${outbound.code}`;
const cached = subscriptionLinkCache.get(cacheKey);
const now = Date.now();
if (cached && cached.expiresAt > now) {
return cached.canCopyLink;
}
const response = await PodkopShellMethods.getOutboundLink(
sectionName,
outbound.code
);
const canCopyLink = response.success && isCopyableProxyLink(response.data.link);
subscriptionLinkCache.set(cacheKey, {
canCopyLink,
expiresAt: now + SUBSCRIPTION_LINK_CACHE_TTL_MS
});
return canCopyLink;
}
async function markSubscriptionCopyableOutbounds(sectionName, outbounds) {
return Promise.all(
outbounds.map(async (outbound) => ({
...outbound,
canCopyLink: await getSubscriptionOutboundCopyState(
sectionName,
outbound
)
}))
);
}
async function getDashboardSections() {
const configSections = await getConfigSections();
const clashProxies = await PodkopShellMethods.getClashApiProxies();
@@ -884,181 +918,136 @@ async function getDashboardSections() {
value
})
);
const data = configSections.filter(
(section) => section.enabled !== "0" && ["proxy", "vpn"].includes(getSectionAction(section))
).map((section) => {
const displayName = getDisplayName(section);
const sectionAction = getSectionAction(section);
const proxyConfigType = getSectionProxyConfigType(section);
if (sectionAction === "vpn") {
const outbound = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
return {
withTagSelect: false,
code: outbound?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds: [
{
code: outbound?.code || section[".name"],
displayName: section.interface || outbound?.value?.name || "",
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: true
}
]
};
}
if (proxyConfigType === "url") {
const outbound = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
const activeConfigs = splitProxyString(section.proxy_string || "");
const proxyDisplayName = getProxyUrlName(activeConfigs?.[0]) || outbound?.value?.name || "";
return {
withTagSelect: false,
code: outbound?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds: [
{
code: outbound?.code || section[".name"],
displayName: proxyDisplayName,
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: true,
link: activeConfigs?.[0] || ""
}
]
};
}
if (proxyConfigType === "outbound") {
const outbound = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
let parsedTag = "";
try {
const parsedOutbound = JSON.parse(section.outbound_json || "{}");
parsedTag = parsedOutbound?.tag ? decodeURIComponent(parsedOutbound.tag) : "";
} catch (_error) {
parsedTag = "";
}
return {
withTagSelect: false,
code: outbound?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds: [
{
code: outbound?.code || section[".name"],
displayName: parsedTag || outbound?.value?.name || "",
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: true
}
]
};
}
if (proxyConfigType === "selector") {
const selector = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
const links = section.selector_proxy_links ?? [];
const outbounds = links.map((link, index) => ({
link,
outbound: proxies.find(
(item) => item.code === `${section[".name"]}-${index + 1}-out`
)
})).map((item) => ({
code: item?.outbound?.code || "",
displayName: getProxyUrlName(item.link) || item?.outbound?.value?.name || "",
latency: item?.outbound?.value?.history?.[0]?.delay || 0,
type: item?.outbound?.value?.type || "",
selected: selector?.value?.now === item?.outbound?.code,
link: item.link
}));
return {
withTagSelect: true,
code: selector?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds
};
}
if (proxyConfigType === "urltest") {
const selector = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
const outbound = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-urltest-out`
);
const outbounds = (outbound?.value?.all ?? []).map((code) => proxies.find((item) => item.code === code)).map((item, index) => ({
code: item?.code || "",
displayName: getProxyUrlName(section.urltest_proxy_links?.[index] || "") || item?.value?.name || "",
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || "",
selected: selector?.value?.now === item?.code,
link: section.urltest_proxy_links?.[index] || ""
}));
return {
withTagSelect: true,
code: selector?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds: [
{
code: outbound?.code || "",
displayName: _("Fastest"),
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: selector?.value?.now === outbound?.code
},
...outbounds
]
};
}
if (proxyConfigType === "subscription") {
const selector = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
const fallbackUrltest = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-urltest-out`
);
const selectorOutbounds = (selector?.value?.all ?? []).flatMap(
(code) => {
const item = proxies.find((proxy) => proxy.code === code);
if (!item) {
return [];
}
const isDefaultFastest = item.code === `${section[".name"]}-urltest-out`;
return [
const data = await Promise.all(
configSections.filter(
(section) => section.enabled !== "0" && ["proxy", "vpn"].includes(getSectionAction(section))
).map(async (section) => {
const displayName = getDisplayName(section);
const sectionAction = getSectionAction(section);
const proxyConfigType = getSectionProxyConfigType(section);
if (sectionAction === "vpn") {
const outbound = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
return {
withTagSelect: false,
code: outbound?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds: [
{
code: item.code,
displayName: isDefaultFastest ? _("Fastest") : item?.value?.name || "",
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || "",
selected: selector?.value?.now === item.code
code: outbound?.code || section[".name"],
displayName: section.interface || outbound?.value?.name || "",
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: true,
canCopyLink: false
}
];
]
};
}
if (proxyConfigType === "url") {
const outbound = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
const activeConfigs = splitProxyString(section.proxy_string || "");
const link = activeConfigs?.[0] || "";
const proxyDisplayName = getProxyUrlName(link) || outbound?.value?.name || "";
return {
withTagSelect: false,
code: outbound?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds: [
{
code: outbound?.code || section[".name"],
displayName: proxyDisplayName,
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: true,
link,
canCopyLink: isCopyableProxyLink(link)
}
]
};
}
if (proxyConfigType === "outbound") {
const outbound = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
let parsedTag = "";
try {
const parsedOutbound = JSON.parse(section.outbound_json || "{}");
parsedTag = parsedOutbound?.tag ? decodeURIComponent(parsedOutbound.tag) : "";
} catch (_error) {
parsedTag = "";
}
);
const outbounds = [
...selectorOutbounds.filter(
(item) => item.type?.toLowerCase() === "urltest"
),
...selectorOutbounds.filter(
(item) => item.type?.toLowerCase() !== "urltest"
)
];
if (outbounds.length === 0 && fallbackUrltest) {
const fallbackOutbounds = (fallbackUrltest?.value?.all ?? []).map((code) => proxies.find((item) => item.code === code)).map((item) => ({
code: item?.code || "",
displayName: item?.value?.name || "",
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || "",
selected: selector?.value?.now === item?.code
}));
return {
withTagSelect: false,
code: outbound?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds: [
{
code: outbound?.code || section[".name"],
displayName: parsedTag || outbound?.value?.name || "",
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: true,
canCopyLink: false
}
]
};
}
if (proxyConfigType === "selector") {
const selector = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
const links = section.selector_proxy_links ?? [];
const outbounds = links.map((link, index) => ({
link,
outbound: proxies.find(
(item) => item.code === `${section[".name"]}-${index + 1}-out`
)
})).map((item) => {
const link = item.link;
return {
code: item?.outbound?.code || "",
displayName: getProxyUrlName(link) || item?.outbound?.value?.name || "",
latency: item?.outbound?.value?.history?.[0]?.delay || 0,
type: item?.outbound?.value?.type || "",
selected: selector?.value?.now === item?.outbound?.code,
link,
canCopyLink: isCopyableProxyLink(link)
};
});
return {
withTagSelect: true,
code: selector?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds
};
}
if (proxyConfigType === "urltest") {
const selector = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
const outbound = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-urltest-out`
);
const outbounds = (outbound?.value?.all ?? []).map((code) => proxies.find((item) => item.code === code)).map((item, index) => {
const link = section.urltest_proxy_links?.[index] || "";
return {
code: item?.code || "",
displayName: getProxyUrlName(link) || item?.value?.name || "",
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || "",
selected: selector?.value?.now === item?.code,
link,
canCopyLink: isCopyableProxyLink(link)
};
});
return {
withTagSelect: true,
code: selector?.code || section[".name"],
@@ -1066,32 +1055,101 @@ async function getDashboardSections() {
displayName,
outbounds: [
{
code: fallbackUrltest?.code || "",
code: outbound?.code || "",
displayName: _("Fastest"),
latency: fallbackUrltest?.value?.history?.[0]?.delay || 0,
type: fallbackUrltest?.value?.type || "",
selected: selector?.value?.now === fallbackUrltest?.code
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: selector?.value?.now === outbound?.code,
canCopyLink: false
},
...fallbackOutbounds
...outbounds
]
};
}
if (proxyConfigType === "subscription") {
const selector = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
const fallbackUrltest = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-urltest-out`
);
const selectorOutbounds = (selector?.value?.all ?? []).flatMap(
(code) => {
const item = proxies.find((proxy) => proxy.code === code);
if (!item) {
return [];
}
const isDefaultFastest = item.code === `${section[".name"]}-urltest-out`;
return [
{
code: item.code,
displayName: isDefaultFastest ? _("Fastest") : item?.value?.name || "",
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || "",
selected: selector?.value?.now === item.code,
canCopyLink: false
}
];
}
);
const outbounds = [
...selectorOutbounds.filter(
(item) => item.type?.toLowerCase() === "urltest"
),
...selectorOutbounds.filter(
(item) => item.type?.toLowerCase() !== "urltest"
)
];
if (outbounds.length === 0 && fallbackUrltest) {
const fallbackOutbounds = (fallbackUrltest?.value?.all ?? []).map((code) => proxies.find((item) => item.code === code)).map((item) => ({
code: item?.code || "",
displayName: item?.value?.name || "",
latency: item?.value?.history?.[0]?.delay || 0,
type: item?.value?.type || "",
selected: selector?.value?.now === item?.code,
canCopyLink: false
}));
return {
withTagSelect: true,
code: selector?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds: [
{
code: fallbackUrltest?.code || "",
displayName: _("Fastest"),
latency: fallbackUrltest?.value?.history?.[0]?.delay || 0,
type: fallbackUrltest?.value?.type || "",
selected: selector?.value?.now === fallbackUrltest?.code,
canCopyLink: false
},
...await markSubscriptionCopyableOutbounds(
section[".name"],
fallbackOutbounds
)
]
};
}
return {
withTagSelect: true,
code: selector?.code || section[".name"],
sectionName: section[".name"],
displayName,
outbounds: await markSubscriptionCopyableOutbounds(
section[".name"],
outbounds
)
};
}
return {
withTagSelect: true,
code: selector?.code || section[".name"],
withTagSelect: false,
code: section[".name"],
sectionName: section[".name"],
displayName,
outbounds
outbounds: []
};
}
return {
withTagSelect: false,
code: section[".name"],
sectionName: section[".name"],
displayName,
outbounds: []
};
});
})
);
return {
success: true,
data
@@ -2520,7 +2578,7 @@ function renderDefaultState({
}
return "pdk_dashboard-page__outbound-grid__item__latency--red";
}
const canCopyLink = outbound.type?.toLowerCase() !== "urltest" && Boolean(outbound.code || outbound.link);
const canCopyLink = Boolean(outbound.canCopyLink) || isCopyableProxyLink(outbound.link);
return E(
"div",
{
@@ -2701,6 +2759,7 @@ function render() {
failed: false,
section: {
code: "",
sectionName: "",
displayName: "",
outbounds: [],
withTagSelect: false
@@ -2709,6 +2768,8 @@ function render() {
},
onChooseOutbound: () => {
},
onCopyOutbound: () => {
},
latencyFetching: false
})
)
@@ -2979,15 +3040,16 @@ async function handleTestProxyLatency(tag) {
});
}
async function handleCopyOutbound(section, outbound) {
if (outbound.link) {
copyToClipboard(outbound.link);
const link = outbound.link;
if (link && isCopyableProxyLink(link)) {
copyToClipboard(link);
return;
}
const response = await PodkopShellMethods.getOutboundLink(
section.sectionName,
outbound.code
);
if (response.success && response.data.link) {
if (response.success && isCopyableProxyLink(response.data.link)) {
copyToClipboard(response.data.link);
return;
}
@@ -3003,6 +3065,7 @@ async function renderSectionsWidget() {
failed: sectionsWidget.failed,
section: {
code: "",
sectionName: "",
displayName: "",
outbounds: [],
withTagSelect: false
@@ -5720,6 +5783,31 @@ function insertIfObj(condition, object) {
return condition ? object : {};
}
// src/helpers/isCopyableProxyLink.ts
var COPYABLE_PROXY_URI_RE = /^(vless|vmess|trojan|ss|ssr|hysteria2|hy2|tuic|socks4|socks4a|socks5):\/\//i;
var COPYABLE_PROXY_OUTBOUND_TYPES = /* @__PURE__ */ new Set([
"vless",
"vmess",
"trojan",
"shadowsocks",
"ss",
"shadowsocksr",
"ssr",
"hysteria2",
"hy2",
"tuic",
"socks",
"socks4",
"socks4a",
"socks5"
]);
function isCopyableProxyLink(link) {
return COPYABLE_PROXY_URI_RE.test((link || "").trim());
}
function isCopyableProxyOutboundType(type) {
return COPYABLE_PROXY_OUTBOUND_TYPES.has((type || "").trim().toLowerCase());
}
// src/main.ts
if (typeof structuredClone !== "function")
globalThis.structuredClone = (obj) => JSON.parse(JSON.stringify(obj));
@@ -5764,6 +5852,8 @@ return baseclass.extend({
injectGlobalStyles,
insertIf,
insertIfObj,
isCopyableProxyLink,
isCopyableProxyOutboundType,
logger,
maskIP,
onMount,