mirror of
https://github.com/decolua/9router.git
synced 2026-05-08 12:01:28 +00:00
Fix bug
This commit is contained in:
62
README.md
62
README.md
@@ -145,7 +145,7 @@ Default URLs:
|
||||
<tr>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/claude.png" width="60" alt="Claude Code"/><br/>
|
||||
<b>Claude Code</b>
|
||||
<b>Claude-Code</b>
|
||||
</td>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/openclaw.png" width="60" alt="OpenClaw"/><br/>
|
||||
@@ -193,7 +193,36 @@ Default URLs:
|
||||
|
||||
## 🌐 Supported Providers
|
||||
|
||||
### 🆓 Free Providers (Unlimited)
|
||||
### 🔐 OAuth Providers
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/claude.png" width="60" alt="Claude Code"/><br/>
|
||||
<b>Claude-Code</b>
|
||||
</td>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/antigravity.png" width="60" alt="Antigravity"/><br/>
|
||||
<b>Antigravity</b>
|
||||
</td>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/codex.png" width="60" alt="Codex"/><br/>
|
||||
<b>Codex</b>
|
||||
</td>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/github.png" width="60" alt="GitHub"/><br/>
|
||||
<b>GitHub</b>
|
||||
</td>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/cursor.png" width="60" alt="Cursor"/><br/>
|
||||
<b>Cursor</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
### 🆓 Free Providers
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
@@ -222,35 +251,6 @@ Default URLs:
|
||||
</table>
|
||||
</div>
|
||||
|
||||
### 🔐 OAuth Providers
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/claude.png" width="60" alt="Claude Code"/><br/>
|
||||
<b>Claude Code</b>
|
||||
</td>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/antigravity.png" width="60" alt="Antigravity"/><br/>
|
||||
<b>Antigravity</b>
|
||||
</td>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/codex.png" width="60" alt="Codex"/><br/>
|
||||
<b>Codex</b>
|
||||
</td>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/github.png" width="60" alt="GitHub"/><br/>
|
||||
<b>GitHub</b>
|
||||
</td>
|
||||
<td align="center" width="120">
|
||||
<img src="./public/providers/cursor.png" width="60" alt="Cursor"/><br/>
|
||||
<b>Cursor</b>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
### 🔑 API Key Providers (40+)
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "9router-app",
|
||||
"version": "0.3.32",
|
||||
"version": "0.3.33",
|
||||
"description": "9Router web dashboard",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -1,5 +1,37 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
// Fetch with timeout wrapper
|
||||
const fetchWithTimeout = (url, options, timeout = 10000) => {
|
||||
return Promise.race([
|
||||
fetch(url, options),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error("Request timeout")), timeout)
|
||||
)
|
||||
]);
|
||||
};
|
||||
|
||||
// Validate URL format
|
||||
const isValidUrl = (url) => {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Parse error details for user-friendly messages
|
||||
const getErrorMessage = (error) => {
|
||||
if (error.cause?.code === "ECONNREFUSED") return "Connection refused - provider node offline or unreachable";
|
||||
if (error.cause?.code === "ENOTFOUND") return "DNS lookup failed - invalid domain or network issue";
|
||||
if (error.cause?.code === "ETIMEDOUT") return "Connection timeout - provider node too slow";
|
||||
if (error.message.includes("timeout")) return "Request timeout (>10s) - provider node not responding";
|
||||
if (error.cause?.code === "CERT_HAS_EXPIRED") return "SSL certificate expired";
|
||||
if (error.cause?.code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") return "SSL certificate verification failed";
|
||||
if (error.cause?.code) return `Network error: ${error.cause.code}`;
|
||||
return "Network connection failed - check URL and network connectivity";
|
||||
};
|
||||
|
||||
// POST /api/provider-nodes/validate - Validate API key against base URL
|
||||
export async function POST(request) {
|
||||
try {
|
||||
@@ -10,6 +42,11 @@ export async function POST(request) {
|
||||
return NextResponse.json({ error: "Base URL and API key required" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
if (!isValidUrl(baseUrl)) {
|
||||
return NextResponse.json({ error: "Invalid URL format" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Anthropic Compatible Validation
|
||||
if (type === "anthropic-compatible") {
|
||||
// Robustly construct URL: remove trailing slash, and remove trailing /messages if user added it
|
||||
@@ -21,7 +58,7 @@ export async function POST(request) {
|
||||
// Use /models endpoint for validation as many compatible providers support it (like OpenAI)
|
||||
const modelsUrl = `${normalizedBase}/models`;
|
||||
|
||||
const res = await fetch(modelsUrl, {
|
||||
const res = await fetchWithTimeout(modelsUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-api-key": apiKey,
|
||||
@@ -30,18 +67,27 @@ export async function POST(request) {
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({ valid: res.ok, error: res.ok ? null : "Invalid API key" });
|
||||
return NextResponse.json({ valid: res.ok, error: res.ok ? null : "Invalid API key or unauthorized" });
|
||||
}
|
||||
|
||||
// OpenAI Compatible Validation (Default)
|
||||
const modelsUrl = `${baseUrl.replace(/\/$/, "")}/models`;
|
||||
const res = await fetch(modelsUrl, {
|
||||
const res = await fetchWithTimeout(modelsUrl, {
|
||||
headers: { "Authorization": `Bearer ${apiKey}` },
|
||||
});
|
||||
|
||||
return NextResponse.json({ valid: res.ok, error: res.ok ? null : "Invalid API key" });
|
||||
return NextResponse.json({ valid: res.ok, error: res.ok ? null : "Invalid API key or unauthorized" });
|
||||
} catch (error) {
|
||||
console.log("Error validating provider node:", error);
|
||||
return NextResponse.json({ error: "Validation failed" }, { status: 500 });
|
||||
const errorMessage = getErrorMessage(error);
|
||||
console.error("Error validating provider node:", {
|
||||
message: error.message,
|
||||
cause: error.cause,
|
||||
code: error.cause?.code,
|
||||
userMessage: errorMessage
|
||||
});
|
||||
return NextResponse.json({
|
||||
valid: false,
|
||||
error: errorMessage
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ async function startServer(apiKey, sudoPassword) {
|
||||
].join("\r\n");
|
||||
const tmpVbs = path.join(os.tmpdir(), `mitm_uac_${Date.now()}.vbs`);
|
||||
fs.writeFileSync(tmpVbs, vbs, "utf8");
|
||||
spawn("wscript.exe", [tmpVbs], { stdio: "ignore", windowsHide: false, detached: true }).unref();
|
||||
spawn("wscript.exe", [tmpVbs], { stdio: "ignore", windowsHide: true, detached: true }).unref();
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const deadline = Date.now() + 90000;
|
||||
@@ -511,7 +511,7 @@ async function stopServer(sudoPassword) {
|
||||
].join("\r\n");
|
||||
const tmpVbs = path.join(os.tmpdir(), "mitm_stop_uac.vbs");
|
||||
fs.writeFileSync(tmpVbs, vbs, "utf8");
|
||||
spawn("wscript.exe", [tmpVbs], { stdio: "ignore", windowsHide: false, detached: true }).unref();
|
||||
spawn("wscript.exe", [tmpVbs], { stdio: "ignore", windowsHide: true, detached: true }).unref();
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const deadline = Date.now() + 30000;
|
||||
|
||||
Reference in New Issue
Block a user