2024-01-15 10:18:04 +08:00
|
|
|
|
import axios, { AxiosInstance } from "axios";
|
2021-12-25 23:20:59 +08:00
|
|
|
|
import { getClashInfo } from "./cmds";
|
2025-03-04 01:01:24 +08:00
|
|
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
|
|
|
|
import { useLockFn } from "ahooks";
|
2024-01-15 10:18:04 +08:00
|
|
|
|
|
2025-04-07 16:50:53 +08:00
|
|
|
|
let instancePromise: Promise<AxiosInstance> = null!;
|
2024-01-15 10:18:04 +08:00
|
|
|
|
|
2025-04-07 16:50:53 +08:00
|
|
|
|
async function getInstancePromise() {
|
2022-12-15 12:22:20 +08:00
|
|
|
|
let server = "";
|
|
|
|
|
|
let secret = "";
|
|
|
|
|
|
|
2021-12-25 23:20:59 +08:00
|
|
|
|
try {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const info = await getClashInfo();
|
2021-12-25 23:20:59 +08:00
|
|
|
|
|
2022-03-10 02:19:06 +08:00
|
|
|
|
if (info?.server) {
|
|
|
|
|
|
server = info.server;
|
2024-01-15 10:18:04 +08:00
|
|
|
|
|
2022-03-10 02:19:06 +08:00
|
|
|
|
// compatible width `external-controller`
|
|
|
|
|
|
if (server.startsWith(":")) server = `127.0.0.1${server}`;
|
|
|
|
|
|
else if (/^\d+$/.test(server)) server = `127.0.0.1:${server}`;
|
|
|
|
|
|
}
|
2022-01-07 23:29:20 +08:00
|
|
|
|
if (info?.secret) secret = info?.secret;
|
2021-12-25 23:20:59 +08:00
|
|
|
|
} catch {}
|
2021-12-25 22:33:29 +08:00
|
|
|
|
|
2025-04-07 16:50:53 +08:00
|
|
|
|
const axiosIns = axios.create({
|
2024-01-15 10:18:04 +08:00
|
|
|
|
baseURL: `http://${server}`,
|
2021-12-25 22:33:29 +08:00
|
|
|
|
headers: secret ? { Authorization: `Bearer ${secret}` } : {},
|
2023-03-16 17:03:12 +08:00
|
|
|
|
timeout: 15000,
|
2021-12-25 22:33:29 +08:00
|
|
|
|
});
|
2024-01-15 10:18:04 +08:00
|
|
|
|
axiosIns.interceptors.response.use((r) => r.data);
|
|
|
|
|
|
return axiosIns;
|
2025-04-07 16:50:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// initialize some information
|
|
|
|
|
|
/// enable force update axiosIns
|
|
|
|
|
|
export const getAxios = async (force: boolean = false) => {
|
|
|
|
|
|
if (!instancePromise || force) {
|
|
|
|
|
|
instancePromise = getInstancePromise();
|
|
|
|
|
|
}
|
|
|
|
|
|
return instancePromise;
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2021-12-25 22:33:29 +08:00
|
|
|
|
|
|
|
|
|
|
/// Get Version
|
2022-12-15 12:22:20 +08:00
|
|
|
|
export const getVersion = async () => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
return instance.get("/version") as Promise<{
|
2021-12-25 22:33:29 +08:00
|
|
|
|
premium: boolean;
|
2022-11-23 17:44:40 +08:00
|
|
|
|
meta?: boolean;
|
2021-12-25 22:33:29 +08:00
|
|
|
|
version: string;
|
|
|
|
|
|
}>;
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2021-12-25 22:33:29 +08:00
|
|
|
|
|
|
|
|
|
|
/// Get current base configs
|
2022-12-15 12:22:20 +08:00
|
|
|
|
export const getClashConfig = async () => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
return instance.get("/configs") as Promise<IConfigData>;
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2021-12-25 22:33:29 +08:00
|
|
|
|
|
2023-12-10 15:22:04 +08:00
|
|
|
|
/// Update geo data
|
|
|
|
|
|
export const updateGeoData = async () => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
return instance.post("/configs/geo");
|
2023-12-10 15:22:04 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2023-12-10 15:57:10 +08:00
|
|
|
|
/// Upgrade clash core
|
|
|
|
|
|
export const upgradeCore = async () => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
return instance.post("/upgrade");
|
2023-12-10 15:57:10 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2021-12-25 22:33:29 +08:00
|
|
|
|
/// Get current rules
|
2022-12-15 12:22:20 +08:00
|
|
|
|
export const getRules = async () => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
const response = await instance.get<any, any>("/rules");
|
|
|
|
|
|
return response?.rules as IRuleItem[];
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2021-12-25 22:33:29 +08:00
|
|
|
|
|
2022-02-16 02:22:01 +08:00
|
|
|
|
/// Get Proxy delay
|
2024-04-09 13:15:45 +08:00
|
|
|
|
export const getProxyDelay = async (
|
|
|
|
|
|
name: string,
|
|
|
|
|
|
url?: string,
|
2024-11-23 11:34:17 +08:00
|
|
|
|
timeout?: number,
|
2024-04-09 13:15:45 +08:00
|
|
|
|
) => {
|
2022-02-16 02:22:01 +08:00
|
|
|
|
const params = {
|
2024-04-09 13:15:45 +08:00
|
|
|
|
timeout: timeout || 10000,
|
2024-11-23 11:34:17 +08:00
|
|
|
|
url: url || "http://cp.cloudflare.com/generate_204",
|
2022-02-16 02:22:01 +08:00
|
|
|
|
};
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
const result = await instance.get(
|
2022-12-15 12:22:20 +08:00
|
|
|
|
`/proxies/${encodeURIComponent(name)}/delay`,
|
2024-11-23 11:34:17 +08:00
|
|
|
|
{ params },
|
2022-12-15 12:22:20 +08:00
|
|
|
|
);
|
2024-01-15 10:18:04 +08:00
|
|
|
|
return result as any as { delay: number };
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2022-02-16 02:22:01 +08:00
|
|
|
|
|
2021-12-25 22:33:29 +08:00
|
|
|
|
/// Update the Proxy Choose
|
2022-12-15 12:22:20 +08:00
|
|
|
|
export const updateProxy = async (group: string, proxy: string) => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
return instance.put(`/proxies/${encodeURIComponent(group)}`, { name: proxy });
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2021-12-25 22:33:29 +08:00
|
|
|
|
|
2022-09-04 22:55:54 +08:00
|
|
|
|
// get proxy
|
2022-12-15 12:22:20 +08:00
|
|
|
|
export const getProxiesInner = async () => {
|
2025-03-04 02:26:26 +08:00
|
|
|
|
const response = await invoke<{ proxies: Record<string, IProxyItem> }>(
|
|
|
|
|
|
"get_proxies",
|
|
|
|
|
|
);
|
|
|
|
|
|
return response.proxies as Record<string, IProxyItem>;
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2022-09-04 22:55:54 +08:00
|
|
|
|
|
2022-09-24 18:48:11 +08:00
|
|
|
|
/// Get the Proxy information
|
2025-03-04 01:01:24 +08:00
|
|
|
|
export const getProxies = async (): Promise<{
|
|
|
|
|
|
global: IProxyGroupItem;
|
|
|
|
|
|
direct: IProxyItem;
|
|
|
|
|
|
groups: IProxyGroupItem[];
|
|
|
|
|
|
records: Record<string, IProxyItem>;
|
|
|
|
|
|
proxies: IProxyItem[];
|
|
|
|
|
|
}> => {
|
2022-09-04 22:55:54 +08:00
|
|
|
|
const [proxyRecord, providerRecord] = await Promise.all([
|
|
|
|
|
|
getProxiesInner(),
|
2024-01-18 01:01:47 +08:00
|
|
|
|
getProxyProviders(),
|
2022-09-04 22:55:54 +08:00
|
|
|
|
]);
|
|
|
|
|
|
// provider name map
|
|
|
|
|
|
const providerMap = Object.fromEntries(
|
|
|
|
|
|
Object.entries(providerRecord).flatMap(([provider, item]) =>
|
2024-11-23 11:34:17 +08:00
|
|
|
|
item.proxies.map((p) => [p.name, { ...p, provider }]),
|
|
|
|
|
|
),
|
2022-09-04 22:55:54 +08:00
|
|
|
|
);
|
2021-12-25 22:33:29 +08:00
|
|
|
|
|
2022-03-05 16:18:44 +08:00
|
|
|
|
// compatible with proxy-providers
|
|
|
|
|
|
const generateItem = (name: string) => {
|
2022-09-04 22:55:54 +08:00
|
|
|
|
if (proxyRecord[name]) return proxyRecord[name];
|
|
|
|
|
|
if (providerMap[name]) return providerMap[name];
|
2024-01-11 12:34:05 +08:00
|
|
|
|
return {
|
|
|
|
|
|
name,
|
|
|
|
|
|
type: "unknown",
|
|
|
|
|
|
udp: false,
|
|
|
|
|
|
xudp: false,
|
|
|
|
|
|
tfo: false,
|
2024-11-16 01:35:22 +08:00
|
|
|
|
mptcp: false,
|
|
|
|
|
|
smux: false,
|
2024-01-11 12:34:05 +08:00
|
|
|
|
history: [],
|
|
|
|
|
|
};
|
2022-03-05 16:18:44 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2022-09-04 22:55:54 +08:00
|
|
|
|
const { GLOBAL: global, DIRECT: direct, REJECT: reject } = proxyRecord;
|
|
|
|
|
|
|
2024-06-15 19:23:58 +08:00
|
|
|
|
let groups: IProxyGroupItem[] = Object.values(proxyRecord).reduce<
|
|
|
|
|
|
IProxyGroupItem[]
|
|
|
|
|
|
>((acc, each) => {
|
|
|
|
|
|
if (each.name !== "GLOBAL" && each.all) {
|
|
|
|
|
|
acc.push({
|
|
|
|
|
|
...each,
|
|
|
|
|
|
all: each.all!.map((item) => generateItem(item)),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2024-06-15 12:22:33 +08:00
|
|
|
|
|
2024-06-15 19:23:58 +08:00
|
|
|
|
return acc;
|
|
|
|
|
|
}, []);
|
2022-09-04 22:55:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (global?.all) {
|
2024-06-15 19:23:58 +08:00
|
|
|
|
let globalGroups: IProxyGroupItem[] = global.all.reduce<IProxyGroupItem[]>(
|
|
|
|
|
|
(acc, name) => {
|
|
|
|
|
|
if (proxyRecord[name]?.all) {
|
|
|
|
|
|
acc.push({
|
|
|
|
|
|
...proxyRecord[name],
|
|
|
|
|
|
all: proxyRecord[name].all!.map((item) => generateItem(item)),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return acc;
|
|
|
|
|
|
},
|
2024-11-23 11:34:17 +08:00
|
|
|
|
[],
|
2024-06-15 19:23:58 +08:00
|
|
|
|
);
|
2024-06-15 12:22:33 +08:00
|
|
|
|
|
|
|
|
|
|
let globalNames = new Set(globalGroups.map((each) => each.name));
|
2024-02-11 17:59:56 +08:00
|
|
|
|
groups = groups
|
|
|
|
|
|
.filter((group) => {
|
2024-06-15 12:22:33 +08:00
|
|
|
|
return !globalNames.has(group.name);
|
2024-02-11 17:59:56 +08:00
|
|
|
|
})
|
|
|
|
|
|
.concat(globalGroups);
|
2021-12-25 22:33:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-02-26 17:39:08 +08:00
|
|
|
|
const proxies = [direct, reject].concat(
|
2022-09-04 22:55:54 +08:00
|
|
|
|
Object.values(proxyRecord).filter(
|
2024-11-23 11:34:17 +08:00
|
|
|
|
(p) => !p.all?.length && p.name !== "DIRECT" && p.name !== "REJECT",
|
|
|
|
|
|
),
|
2022-02-26 17:39:08 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
2022-11-19 17:22:29 +08:00
|
|
|
|
const _global: IProxyGroupItem = {
|
2022-10-13 11:54:52 +08:00
|
|
|
|
...global,
|
|
|
|
|
|
all: global?.all?.map((item) => generateItem(item)) || [],
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return { global: _global, direct, groups, records: proxyRecord, proxies };
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2022-03-05 16:18:44 +08:00
|
|
|
|
|
2022-09-04 22:55:54 +08:00
|
|
|
|
// get proxy providers
|
2024-01-18 01:01:47 +08:00
|
|
|
|
export const getProxyProviders = async () => {
|
2025-03-04 02:26:26 +08:00
|
|
|
|
const response = await invoke<{
|
|
|
|
|
|
providers: Record<string, IProxyProviderItem>;
|
|
|
|
|
|
}>("get_providers_proxies");
|
|
|
|
|
|
const providers = response.providers as Record<string, IProxyProviderItem>;
|
2024-01-18 01:01:47 +08:00
|
|
|
|
|
|
|
|
|
|
return Object.fromEntries(
|
|
|
|
|
|
Object.entries(providers).filter(([key, item]) => {
|
|
|
|
|
|
const type = item.vehicleType.toLowerCase();
|
|
|
|
|
|
return type === "http" || type === "file";
|
2024-11-23 11:34:17 +08:00
|
|
|
|
}),
|
2024-01-18 01:01:47 +08:00
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const getRuleProviders = async () => {
|
|
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
const response = await instance.get<any, any>("/providers/rules");
|
|
|
|
|
|
|
|
|
|
|
|
const providers = (response.providers || {}) as Record<
|
|
|
|
|
|
string,
|
|
|
|
|
|
IRuleProviderItem
|
|
|
|
|
|
>;
|
2022-10-11 22:51:18 +08:00
|
|
|
|
|
|
|
|
|
|
return Object.fromEntries(
|
|
|
|
|
|
Object.entries(providers).filter(([key, item]) => {
|
|
|
|
|
|
const type = item.vehicleType.toLowerCase();
|
|
|
|
|
|
return type === "http" || type === "file";
|
2024-11-23 11:34:17 +08:00
|
|
|
|
}),
|
2022-10-11 22:51:18 +08:00
|
|
|
|
);
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2022-03-18 14:43:22 +08:00
|
|
|
|
|
2022-09-04 22:55:54 +08:00
|
|
|
|
// proxy providers health check
|
2022-12-15 12:22:20 +08:00
|
|
|
|
export const providerHealthCheck = async (name: string) => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
return instance.get(
|
2024-11-23 11:34:17 +08:00
|
|
|
|
`/providers/proxies/${encodeURIComponent(name)}/healthcheck`,
|
2022-03-30 01:30:22 +08:00
|
|
|
|
);
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2022-03-30 01:30:22 +08:00
|
|
|
|
|
2024-01-18 01:01:47 +08:00
|
|
|
|
export const proxyProviderUpdate = async (name: string) => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
return instance.put(`/providers/proxies/${encodeURIComponent(name)}`);
|
2023-08-05 21:38:44 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2024-01-18 01:01:47 +08:00
|
|
|
|
export const ruleProviderUpdate = async (name: string) => {
|
|
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
return instance.put(`/providers/rules/${encodeURIComponent(name)}`);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2022-12-15 12:22:20 +08:00
|
|
|
|
export const getConnections = async () => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
const result = await instance.get("/connections");
|
|
|
|
|
|
return result as any as IConnections;
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2022-11-10 01:27:05 +08:00
|
|
|
|
|
2022-03-18 14:43:22 +08:00
|
|
|
|
// Close specific connection
|
2022-12-15 12:22:20 +08:00
|
|
|
|
export const deleteConnection = async (id: string) => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
await instance.delete<any, any>(`/connections/${encodeURIComponent(id)}`);
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2022-03-18 14:43:22 +08:00
|
|
|
|
|
|
|
|
|
|
// Close all connections
|
2022-12-15 12:22:20 +08:00
|
|
|
|
export const closeAllConnections = async () => {
|
2024-01-15 10:18:04 +08:00
|
|
|
|
const instance = await getAxios();
|
2025-03-14 13:31:34 +08:00
|
|
|
|
await instance.delete("/connections");
|
2022-12-15 12:22:20 +08:00
|
|
|
|
};
|
2024-04-09 13:15:45 +08:00
|
|
|
|
|
|
|
|
|
|
// Get Group Proxy Delays
|
|
|
|
|
|
export const getGroupProxyDelays = async (
|
|
|
|
|
|
groupName: string,
|
|
|
|
|
|
url?: string,
|
2024-11-23 11:34:17 +08:00
|
|
|
|
timeout?: number,
|
2024-04-09 13:15:45 +08:00
|
|
|
|
) => {
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
timeout: timeout || 10000,
|
2024-11-23 11:34:17 +08:00
|
|
|
|
url: url || "http://cp.cloudflare.com/generate_204",
|
2024-04-09 13:15:45 +08:00
|
|
|
|
};
|
2025-03-09 04:22:34 +08:00
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`[API] 获取代理组延迟,组: ${groupName}, URL: ${params.url}, 超时: ${params.timeout}ms`,
|
2024-04-09 13:15:45 +08:00
|
|
|
|
);
|
2025-03-09 04:22:34 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`[API] 发送HTTP请求: GET /group/${encodeURIComponent(groupName)}/delay`,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const result = await instance.get(
|
|
|
|
|
|
`/group/${encodeURIComponent(groupName)}/delay`,
|
|
|
|
|
|
{ params },
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`[API] 获取代理组延迟成功,组: ${groupName}, 结果数量:`,
|
|
|
|
|
|
Object.keys(result || {}).length,
|
|
|
|
|
|
);
|
|
|
|
|
|
return result as any as Record<string, number>;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`[API] 获取代理组延迟失败,组: ${groupName}`, error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
2024-04-09 13:15:45 +08:00
|
|
|
|
};
|
2024-06-26 17:44:42 +08:00
|
|
|
|
|
|
|
|
|
|
// Is debug enabled
|
|
|
|
|
|
export const isDebugEnabled = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
await instance.get("/debug/pprof");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// GC
|
|
|
|
|
|
export const gc = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const instance = await getAxios();
|
|
|
|
|
|
await instance.put("/debug/gc");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`Error gcing: ${error}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-03-14 13:31:34 +08:00
|
|
|
|
|
2025-05-14 23:13:52 +08:00
|
|
|
|
// Get current IP and geolocation information (refactored IP detection with service-specific mappings)
|
2025-05-14 20:32:45 +08:00
|
|
|
|
interface IpInfo {
|
|
|
|
|
|
ip: string;
|
|
|
|
|
|
country_code: string;
|
|
|
|
|
|
country: string;
|
|
|
|
|
|
region: string;
|
|
|
|
|
|
city: string;
|
|
|
|
|
|
organization: string;
|
|
|
|
|
|
asn: number;
|
|
|
|
|
|
asn_organization: string;
|
|
|
|
|
|
longitude: number;
|
|
|
|
|
|
latitude: number;
|
|
|
|
|
|
timezone: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-14 23:13:52 +08:00
|
|
|
|
// IP检测服务配置
|
|
|
|
|
|
interface ServiceConfig {
|
|
|
|
|
|
url: string;
|
|
|
|
|
|
mapping: (data: any) => IpInfo;
|
|
|
|
|
|
timeout?: number; // 保留timeout字段(如有需要)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 可用的IP检测服务列表及字段映射
|
|
|
|
|
|
const IP_CHECK_SERVICES: ServiceConfig[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
url: "https://api.ip.sb/geoip",
|
|
|
|
|
|
mapping: (data) => ({
|
|
|
|
|
|
ip: data.ip || '',
|
|
|
|
|
|
country_code: data.country_code || '',
|
|
|
|
|
|
country: data.country || '',
|
|
|
|
|
|
region: data.region || '',
|
|
|
|
|
|
city: data.city || '',
|
|
|
|
|
|
organization: data.organization || data.isp || '',
|
|
|
|
|
|
asn: data.asn || 0,
|
|
|
|
|
|
asn_organization: data.asn_organization || '',
|
|
|
|
|
|
longitude: data.longitude || 0,
|
|
|
|
|
|
latitude: data.latitude || 0,
|
|
|
|
|
|
timezone: data.timezone || '',
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
url: "https://ipapi.co/json",
|
|
|
|
|
|
mapping: (data) => ({
|
|
|
|
|
|
ip: data.ip || '',
|
|
|
|
|
|
country_code: data.country_code || '',
|
|
|
|
|
|
country: data.country_name || '',
|
|
|
|
|
|
region: data.region || '',
|
|
|
|
|
|
city: data.city || '',
|
|
|
|
|
|
organization: data.org || '',
|
|
|
|
|
|
asn: data.asn? parseInt(data.asn.replace('AS', '')) : 0,
|
|
|
|
|
|
asn_organization: data.org || '',
|
|
|
|
|
|
longitude: data.longitude || 0,
|
|
|
|
|
|
latitude: data.latitude || 0,
|
|
|
|
|
|
timezone: data.timezone || '',
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2025-05-16 12:26:50 +08:00
|
|
|
|
url: "https://api.ipapi.is/",
|
|
|
|
|
|
mapping: (data) => ({
|
|
|
|
|
|
ip: data.ip || '',
|
|
|
|
|
|
country_code: data.location?.country_code || '',
|
|
|
|
|
|
country: data.location?.country || '',
|
|
|
|
|
|
region: data.location?.state || '',
|
|
|
|
|
|
city: data.location?.city || '',
|
|
|
|
|
|
organization: data.asn?.org || data.company?.name || '',
|
|
|
|
|
|
asn: data.asn?.asn || 0,
|
|
|
|
|
|
asn_organization: data.asn?.org || '',
|
|
|
|
|
|
longitude: data.location?.longitude || 0,
|
|
|
|
|
|
latitude: data.location?.latitude || 0,
|
|
|
|
|
|
timezone: data.location?.timezone || '',
|
|
|
|
|
|
}),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
url: "https://ipwho.is/",
|
|
|
|
|
|
mapping: (data) => ({
|
|
|
|
|
|
ip: data.ip || '',
|
|
|
|
|
|
country_code: data.country_code || '',
|
|
|
|
|
|
country: data.country || '',
|
|
|
|
|
|
region: data.region || '',
|
|
|
|
|
|
city: data.city || '',
|
|
|
|
|
|
organization: data.connection?.org || data.connection?.isp || '',
|
|
|
|
|
|
asn: data.connection?.asn || 0,
|
|
|
|
|
|
asn_organization: data.connection?.isp || '',
|
|
|
|
|
|
longitude: data.longitude || 0,
|
|
|
|
|
|
latitude: data.latitude || 0,
|
|
|
|
|
|
timezone: data.timezone?.id || '',
|
|
|
|
|
|
}),
|
2025-05-14 23:13:52 +08:00
|
|
|
|
},
|
2025-05-14 20:32:45 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
2025-05-16 12:26:50 +08:00
|
|
|
|
// 随机性服务列表洗牌函数
|
2025-05-14 20:32:45 +08:00
|
|
|
|
function shuffleServices() {
|
2025-05-16 12:26:50 +08:00
|
|
|
|
// 过滤无效服务并确保每个元素符合ServiceConfig接口
|
|
|
|
|
|
const validServices = IP_CHECK_SERVICES.filter(
|
|
|
|
|
|
(service): service is ServiceConfig =>
|
|
|
|
|
|
service !== null &&
|
|
|
|
|
|
service !== undefined &&
|
|
|
|
|
|
typeof service.url === 'string' &&
|
|
|
|
|
|
typeof service.mapping === 'function' // 添加对mapping属性的检查
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (validServices.length === 0) {
|
|
|
|
|
|
console.error('No valid services found in IP_CHECK_SERVICES');
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用单一Fisher-Yates洗牌算法,增强随机性
|
|
|
|
|
|
const shuffled = [...validServices];
|
|
|
|
|
|
const length = shuffled.length;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用多个种子进行多次洗牌
|
|
|
|
|
|
const seeds = [
|
|
|
|
|
|
Math.random(),
|
|
|
|
|
|
Date.now() / 1000,
|
|
|
|
|
|
performance.now() / 1000
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (const seed of seeds) {
|
|
|
|
|
|
const prng = createPrng(seed);
|
|
|
|
|
|
|
|
|
|
|
|
// Fisher-Yates洗牌算法
|
|
|
|
|
|
for (let i = length - 1; i > 0; i--) {
|
|
|
|
|
|
const j = Math.floor(prng() * (i + 1));
|
|
|
|
|
|
|
|
|
|
|
|
// 使用临时变量进行交换,避免解构赋值可能的问题
|
|
|
|
|
|
const temp = shuffled[i];
|
|
|
|
|
|
shuffled[i] = shuffled[j];
|
|
|
|
|
|
shuffled[j] = temp;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return shuffled;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建一个简单的随机数生成器
|
|
|
|
|
|
function createPrng(seed: number): () => number {
|
|
|
|
|
|
// 使用xorshift32算法
|
|
|
|
|
|
let state = seed >>> 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果种子为0,设置一个默认值
|
|
|
|
|
|
if (state === 0) state = 123456789;
|
|
|
|
|
|
|
|
|
|
|
|
return function() {
|
|
|
|
|
|
state ^= state << 13;
|
|
|
|
|
|
state ^= state >>> 17;
|
|
|
|
|
|
state ^= state << 5;
|
|
|
|
|
|
return (state >>> 0) / 4294967296;
|
|
|
|
|
|
};
|
2025-05-14 20:32:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-14 23:13:52 +08:00
|
|
|
|
// 获取当前IP和地理位置信息
|
2025-05-14 20:32:45 +08:00
|
|
|
|
export const getIpInfo = async (): Promise<IpInfo> => {
|
|
|
|
|
|
// 配置参数
|
2025-04-20 10:34:43 +08:00
|
|
|
|
const maxRetries = 3;
|
2025-05-14 20:32:45 +08:00
|
|
|
|
const serviceTimeout = 5000;
|
2025-05-14 23:13:52 +08:00
|
|
|
|
const overallTimeout = 20000; // 增加总超时时间以容纳延迟
|
2025-05-14 20:32:45 +08:00
|
|
|
|
|
|
|
|
|
|
const overallTimeoutController = new AbortController();
|
|
|
|
|
|
const overallTimeoutId = setTimeout(() => {
|
|
|
|
|
|
overallTimeoutController.abort();
|
|
|
|
|
|
}, overallTimeout);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const shuffledServices = shuffleServices();
|
|
|
|
|
|
let lastError: Error | null = null;
|
|
|
|
|
|
|
2025-05-14 23:13:52 +08:00
|
|
|
|
for (const service of shuffledServices) {
|
|
|
|
|
|
console.log(`尝试IP检测服务: ${service.url}`);
|
|
|
|
|
|
|
2025-05-14 20:32:45 +08:00
|
|
|
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
|
|
|
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
2025-05-14 23:13:52 +08:00
|
|
|
|
|
2025-05-14 20:32:45 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const timeoutController = new AbortController();
|
|
|
|
|
|
timeoutId = setTimeout(() => {
|
|
|
|
|
|
timeoutController.abort();
|
2025-05-14 23:13:52 +08:00
|
|
|
|
}, service.timeout || serviceTimeout);
|
2025-05-14 20:32:45 +08:00
|
|
|
|
|
2025-05-14 23:13:52 +08:00
|
|
|
|
const response = await axios.get(service.url, {
|
2025-05-14 20:32:45 +08:00
|
|
|
|
signal: timeoutController.signal,
|
2025-05-14 23:13:52 +08:00
|
|
|
|
timeout: service.timeout || serviceTimeout,
|
|
|
|
|
|
// 移除了headers参数(默认会使用axios的默认User-Agent)
|
2025-05-14 20:32:45 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (timeoutId) clearTimeout(timeoutId);
|
|
|
|
|
|
|
|
|
|
|
|
if (response.data && response.data.ip) {
|
2025-05-14 23:13:52 +08:00
|
|
|
|
console.log(`IP检测成功,使用服务: ${service.url}`);
|
|
|
|
|
|
return service.mapping(response.data);
|
2025-05-14 20:32:45 +08:00
|
|
|
|
} else {
|
2025-05-14 23:13:52 +08:00
|
|
|
|
throw new Error(`无效的响应格式 from ${service.url}`);
|
2025-05-14 20:32:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
if (timeoutId) clearTimeout(timeoutId);
|
2025-05-14 23:13:52 +08:00
|
|
|
|
|
2025-05-14 20:32:45 +08:00
|
|
|
|
lastError = error;
|
|
|
|
|
|
console.log(
|
2025-05-14 23:13:52 +08:00
|
|
|
|
`尝试 ${attempt + 1}/${maxRetries} 失败 (${service.url}):`,
|
2025-05-14 20:32:45 +08:00
|
|
|
|
error.message
|
|
|
|
|
|
);
|
2025-05-14 23:13:52 +08:00
|
|
|
|
|
2025-05-14 20:32:45 +08:00
|
|
|
|
if (error.name === "AbortError") {
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
2025-05-14 23:13:52 +08:00
|
|
|
|
|
2025-05-14 20:32:45 +08:00
|
|
|
|
if (attempt < maxRetries - 1) {
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-04-20 10:34:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-14 20:32:45 +08:00
|
|
|
|
if (lastError) {
|
|
|
|
|
|
throw new Error(`所有IP检测服务都失败: ${lastError.message}`);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error('没有可用的IP检测服务');
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
clearTimeout(overallTimeoutId);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|