feat: add CORS proxy for subscription fetch
This commit is contained in:
+14
-28
@@ -431,15 +431,17 @@
|
||||
if (!url) return toast('请输入订阅链接', 'error');
|
||||
|
||||
try {
|
||||
toast('正在从浏览器获取订阅...', 'info');
|
||||
toast('正在获取订阅...', 'info');
|
||||
|
||||
// Fetch subscription from browser side (avoids server network issues)
|
||||
const resp = await fetch(url, {
|
||||
headers: { 'User-Agent': 'ClashForAndroid/2.5.12' },
|
||||
mode: 'cors',
|
||||
});
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
let content = await resp.text();
|
||||
// Use server-side proxy to fetch (bypasses CORS)
|
||||
const fetchRes = await api('POST', '/api/proxy-fetch', { url });
|
||||
if (fetchRes.error) throw new Error(fetchRes.error);
|
||||
let content = fetchRes.content;
|
||||
|
||||
// Check if we got HTML instead of YAML
|
||||
if (content.trim().startsWith('<!DOCTYPE') || content.trim().startsWith('<html')) {
|
||||
throw new Error('订阅地址返回了网页而非配置,请检查链接是否正确');
|
||||
}
|
||||
|
||||
// Try base64 decode
|
||||
try {
|
||||
@@ -447,13 +449,13 @@
|
||||
if (decoded.includes('://') || decoded.includes('proxies')) content = decoded;
|
||||
} catch {}
|
||||
|
||||
if (!content.includes('proxies') && !content.includes('port:')) {
|
||||
throw new Error('无效的订阅内容');
|
||||
if (!content.includes('proxies') && !content.includes('port:') && !content.includes('mixed-port:')) {
|
||||
throw new Error('无效的订阅内容,请检查链接');
|
||||
}
|
||||
|
||||
toast('订阅获取成功,正在推送到 mihomo...', 'info');
|
||||
|
||||
// Send content to mihomo via our server API
|
||||
// Push content to mihomo
|
||||
const applyRes = await api('POST', '/api/subscription/push', { content, url });
|
||||
if (applyRes.error) throw new Error(applyRes.error);
|
||||
|
||||
@@ -463,23 +465,7 @@
|
||||
loadSubscriptions();
|
||||
loadDashboard();
|
||||
} catch(e) {
|
||||
// CORS blocked - fallback to server-side fetch
|
||||
if (e.message.includes('Failed to fetch') || e.message.includes('NetworkError')) {
|
||||
toast('浏览器无法直接获取(CORS),改用服务器获取...', 'info');
|
||||
try {
|
||||
const res = await api('POST', '/api/subscription/import', { url });
|
||||
if (res.error) throw new Error(res.error);
|
||||
const applyRes = await api('POST', '/api/subscription/apply', { url });
|
||||
if (applyRes.error) throw new Error(applyRes.error);
|
||||
toast('🎉 订阅导入成功!', 'success');
|
||||
document.getElementById('sub-url').value = '';
|
||||
loadSubscriptions();
|
||||
} catch(e2) {
|
||||
toast('导入失败: ' + e2.message, 'error');
|
||||
}
|
||||
} else {
|
||||
toast('导入失败: ' + e.message, 'error');
|
||||
}
|
||||
toast('导入失败: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -355,6 +355,21 @@ async function handleApi(req, res, apiPath) {
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/proxy-fetch - CORS proxy: fetch URL on behalf of browser
|
||||
if (apiPath === '/api/proxy-fetch' && method === 'POST') {
|
||||
const body = JSON.parse(await readBody(req));
|
||||
const { url } = body;
|
||||
if (!url) return sendError(res, 'Missing url', 400);
|
||||
|
||||
try {
|
||||
const result = await fetchUrl(url, { headers: { 'User-Agent': 'ClashForAndroid/2.5.12' } });
|
||||
const content = result.body.toString('utf-8');
|
||||
return sendJson(res, { ok: true, content, status: result.status });
|
||||
} catch (e) {
|
||||
return sendError(res, 'Fetch failed: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/subscription/push - Push subscription content directly to mihomo
|
||||
if (apiPath === '/api/subscription/push' && method === 'POST') {
|
||||
const body = JSON.parse(await readBody(req));
|
||||
|
||||
Reference in New Issue
Block a user