feat(proxy-pools): add support for deno deploy relays and fix layout overflow issues in proxy pools dashboard (#1437)
Co-authored-by: TD <tho.din@inno.ai.vn>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
|
||||||
import { Badge, Button, Card, CardSkeleton, Input, Modal, Toggle, ConfirmModal } from "@/shared/components";
|
import { Badge, Button, Card, CardSkeleton, Input, Modal, Toggle, ConfirmModal } from "@/shared/components";
|
||||||
import { useNotificationStore } from "@/store/notificationStore";
|
import { useNotificationStore } from "@/store/notificationStore";
|
||||||
|
|
||||||
@@ -34,11 +34,14 @@ export default function ProxyPoolsPage() {
|
|||||||
const [showBatchImportModal, setShowBatchImportModal] = useState(false);
|
const [showBatchImportModal, setShowBatchImportModal] = useState(false);
|
||||||
const [showVercelModal, setShowVercelModal] = useState(false);
|
const [showVercelModal, setShowVercelModal] = useState(false);
|
||||||
const [showCloudflareModal, setShowCloudflareModal] = useState(false);
|
const [showCloudflareModal, setShowCloudflareModal] = useState(false);
|
||||||
|
const [showDenoModal, setShowDenoModal] = useState(false);
|
||||||
|
const [showRelayMenu, setShowRelayMenu] = useState(false);
|
||||||
const [editingProxyPool, setEditingProxyPool] = useState(null);
|
const [editingProxyPool, setEditingProxyPool] = useState(null);
|
||||||
const [formData, setFormData] = useState(normalizeFormData());
|
const [formData, setFormData] = useState(normalizeFormData());
|
||||||
const [batchImportText, setBatchImportText] = useState("");
|
const [batchImportText, setBatchImportText] = useState("");
|
||||||
const [vercelForm, setVercelForm] = useState({ vercelToken: "", projectName: "vercel-relay" });
|
const [vercelForm, setVercelForm] = useState({ vercelToken: "", projectName: "vercel-relay" });
|
||||||
const [cloudflareForm, setCloudflareForm] = useState({ accountId: "", apiToken: "", projectName: "cloudflare-relay" });
|
const [cloudflareForm, setCloudflareForm] = useState({ accountId: "", apiToken: "", projectName: "cloudflare-relay" });
|
||||||
|
const [denoForm, setDenoForm] = useState({ denoToken: "", orgDomain: "", projectName: "" });
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [importing, setImporting] = useState(false);
|
const [importing, setImporting] = useState(false);
|
||||||
const [deploying, setDeploying] = useState(false);
|
const [deploying, setDeploying] = useState(false);
|
||||||
@@ -48,8 +51,21 @@ export default function ProxyPoolsPage() {
|
|||||||
const [healthProgress, setHealthProgress] = useState({ current: 0, total: 0 });
|
const [healthProgress, setHealthProgress] = useState({ current: 0, total: 0 });
|
||||||
const [bulkBusy, setBulkBusy] = useState(false);
|
const [bulkBusy, setBulkBusy] = useState(false);
|
||||||
const [confirmState, setConfirmState] = useState(null);
|
const [confirmState, setConfirmState] = useState(null);
|
||||||
|
const relayMenuRef = useRef(null);
|
||||||
const notify = useNotificationStore();
|
const notify = useNotificationStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (e) => {
|
||||||
|
if (relayMenuRef.current && !relayMenuRef.current.contains(e.target)) {
|
||||||
|
setShowRelayMenu(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (showRelayMenu) {
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
}
|
||||||
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
}, [showRelayMenu]);
|
||||||
|
|
||||||
const fetchProxyPools = useCallback(async () => {
|
const fetchProxyPools = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/proxy-pools?includeUsage=true", { cache: "no-store" });
|
const res = await fetch("/api/proxy-pools?includeUsage=true", { cache: "no-store" });
|
||||||
@@ -346,6 +362,16 @@ export default function ProxyPoolsPage() {
|
|||||||
setShowCloudflareModal(false);
|
setShowCloudflareModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openDenoModal = () => {
|
||||||
|
setDenoForm({ denoToken: "", orgDomain: "", projectName: "" });
|
||||||
|
setShowDenoModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDenoModal = () => {
|
||||||
|
if (deploying) return;
|
||||||
|
setShowDenoModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleVercelDeploy = async () => {
|
const handleVercelDeploy = async () => {
|
||||||
if (!vercelForm.vercelToken.trim()) return;
|
if (!vercelForm.vercelToken.trim()) return;
|
||||||
setDeploying(true);
|
setDeploying(true);
|
||||||
@@ -396,6 +422,31 @@ export default function ProxyPoolsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDenoDeploy = async () => {
|
||||||
|
if (!denoForm.denoToken.trim()) return;
|
||||||
|
setDeploying(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/proxy-pools/deno-deploy", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(denoForm),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.ok) {
|
||||||
|
await fetchProxyPools();
|
||||||
|
closeDenoModal();
|
||||||
|
notify.success(`Deployed: ${data.deployUrl}`);
|
||||||
|
} else {
|
||||||
|
notify.error(data.error || "Deploy failed");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error deploying Deno relay:", error);
|
||||||
|
notify.error("Deploy failed");
|
||||||
|
} finally {
|
||||||
|
setDeploying(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const parseProxyLine = (line) => {
|
const parseProxyLine = (line) => {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (!trimmed) return null;
|
if (!trimmed) return null;
|
||||||
@@ -532,12 +583,55 @@ export default function ProxyPoolsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-2 sm:flex sm:items-center">
|
<div className="grid grid-cols-1 gap-2 sm:flex sm:items-center">
|
||||||
<Button size="sm" variant="secondary" icon="cloud" onClick={openCloudflareModal}>
|
<div className="relative" ref={relayMenuRef}>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
icon="rocket_launch"
|
||||||
|
onClick={() => setShowRelayMenu(!showRelayMenu)}
|
||||||
|
>
|
||||||
|
Deploy Relay
|
||||||
|
<span className="material-symbols-outlined ml-1 text-[18px]">
|
||||||
|
{showRelayMenu ? "expand_less" : "expand_more"}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{showRelayMenu && (
|
||||||
|
<div className="absolute left-0 top-full z-50 mt-1 w-48 rounded-xl border border-black/10 bg-white p-1 shadow-xl dark:border-white/10 dark:bg-zinc-900 sm:left-auto sm:right-0">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
openCloudflareModal();
|
||||||
|
setShowRelayMenu(false);
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-text-main transition-colors hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[20px] text-orange-500">cloud</span>
|
||||||
Cloudflare Relay
|
Cloudflare Relay
|
||||||
</Button>
|
</button>
|
||||||
<Button size="sm" variant="secondary" icon="cloud_upload" onClick={openVercelModal}>
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
openVercelModal();
|
||||||
|
setShowRelayMenu(false);
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-text-main transition-colors hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[20px] text-blue-500">cloud_upload</span>
|
||||||
Vercel Relay
|
Vercel Relay
|
||||||
</Button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
openDenoModal();
|
||||||
|
setShowRelayMenu(false);
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-text-main transition-colors hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-[20px] text-green-500">terminal</span>
|
||||||
|
Deno Relay
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button size="sm" variant="secondary" icon="upload" onClick={openBatchImportModal}>
|
<Button size="sm" variant="secondary" icon="upload" onClick={openBatchImportModal}>
|
||||||
Batch Import
|
Batch Import
|
||||||
</Button>
|
</Button>
|
||||||
@@ -829,6 +923,70 @@ export default function ProxyPoolsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
isOpen={showDenoModal}
|
||||||
|
title="Deploy Deno Relay"
|
||||||
|
onClose={closeDenoModal}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="rounded-lg bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 p-3 flex flex-col gap-1.5">
|
||||||
|
<p className="text-sm text-text-main font-medium">What is Deno Relay?</p>
|
||||||
|
<p className="text-xs text-text-muted">
|
||||||
|
Deploys a relay worker to Deno Deploy's global edge network. All AI provider requests are forwarded through Deno's edge, masking your real IP.
|
||||||
|
</p>
|
||||||
|
<ul className="text-xs text-text-muted list-disc pl-4 space-y-0.5">
|
||||||
|
<li>Deno Deploy v2 runs on a high-performance global edge network</li>
|
||||||
|
<li>Free tier: 1M requests & 100GiB outbound traffic per month</li>
|
||||||
|
<li>No per-request CPU time limits (unlike Vercel/Cloudflare)</li>
|
||||||
|
<li>Support up to 20 active apps & 50 custom domains</li>
|
||||||
|
<li>Deploy multiple relays for maximum IP diversity</li>
|
||||||
|
</ul>
|
||||||
|
<div className="mt-2 pt-2 border-t border-black/10 dark:border-white/10 text-xs text-text-muted">
|
||||||
|
<p className="font-medium text-text-main mb-1">How to generate API token:</p>
|
||||||
|
<ol className="list-decimal pl-4 space-y-0.5">
|
||||||
|
<li>Go to <b>console.deno.com</b></li>
|
||||||
|
<li>Select your <b>Organization</b> → <b>Settings</b> → <b>Organization Tokens</b></li>
|
||||||
|
<li>Create a <b>Organization Token</b> (prefix <b>ddo_</b>)</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
label="Deno Deploy API Token"
|
||||||
|
value={denoForm.denoToken}
|
||||||
|
onChange={(e) => setDenoForm((prev) => ({ ...prev, denoToken: e.target.value }))}
|
||||||
|
placeholder="ddo_xxxxxxxxxxxxxxxx"
|
||||||
|
hint={<>Token is used once for deployment, not stored. Found in Organization Settings.</>}
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Organization Domain"
|
||||||
|
value={denoForm.orgDomain}
|
||||||
|
onChange={(e) => setDenoForm((prev) => ({ ...prev, orgDomain: e.target.value }))}
|
||||||
|
placeholder="your-org.deno.net"
|
||||||
|
hint="Organization's default domain. Your relay URL will be in the format: https://my-relay.your-org.deno.net"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="App Name"
|
||||||
|
value={denoForm.projectName}
|
||||||
|
onChange={(e) => setDenoForm((prev) => ({ ...prev, projectName: e.target.value }))}
|
||||||
|
placeholder="deno-relay"
|
||||||
|
hint="Unique app name. Leave empty for auto-generated name."
|
||||||
|
/>
|
||||||
|
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
onClick={handleDenoDeploy}
|
||||||
|
disabled={!denoForm.denoToken.trim() || !denoForm.orgDomain.trim() || deploying}
|
||||||
|
>
|
||||||
|
{deploying ? "Deploying..." : "Deploy Relay"}
|
||||||
|
</Button>
|
||||||
|
<Button fullWidth variant="ghost" onClick={closeDenoModal} disabled={deploying}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={showFormModal}
|
isOpen={showFormModal}
|
||||||
title={editingProxyPool ? "Edit Proxy Pool" : "Add Proxy Pool"}
|
title={editingProxyPool ? "Edit Proxy Pool" : "Add Proxy Pool"}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export async function POST(request, { params }) {
|
|||||||
return NextResponse.json({ error: "Proxy pool not found" }, { status: 404 });
|
return NextResponse.json({ error: "Proxy pool not found" }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = proxyPool.type === "vercel" || proxyPool.type === "cloudflare"
|
const result = proxyPool.type === "vercel" || proxyPool.type === "cloudflare" || proxyPool.type === "deno"
|
||||||
? await testVercelRelay(proxyPool.proxyUrl)
|
? await testVercelRelay(proxyPool.proxyUrl)
|
||||||
: await testProxyUrl({ proxyUrl: proxyPool.proxyUrl });
|
: await testProxyUrl({ proxyUrl: proxyPool.proxyUrl });
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
|||||||
175
src/app/api/proxy-pools/deno-deploy/route.js
Normal file
175
src/app/api/proxy-pools/deno-deploy/route.js
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { createProxyPool } from "@/models";
|
||||||
|
|
||||||
|
const DENO_V2_API = "https://api.deno.com/v2";
|
||||||
|
|
||||||
|
const DENO_RELAY_CODE = `Deno.serve(async (request) => {
|
||||||
|
const target = request.headers.get("x-relay-target");
|
||||||
|
const relayPath = request.headers.get("x-relay-path") || "/";
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
return new Response(JSON.stringify({ error: "Missing x-relay-target header" }), {
|
||||||
|
status: 400,
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetUrl = target.replace(/\\/$/, "") + relayPath;
|
||||||
|
const newHeaders = new Headers(request.headers);
|
||||||
|
newHeaders.delete("x-relay-target");
|
||||||
|
newHeaders.delete("x-relay-path");
|
||||||
|
newHeaders.delete("host");
|
||||||
|
|
||||||
|
const init = {
|
||||||
|
method: request.method,
|
||||||
|
headers: newHeaders,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
||||||
|
init.body = request.body;
|
||||||
|
init.duplex = "half";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(targetUrl, init);
|
||||||
|
return new Response(response.body, {
|
||||||
|
status: response.status,
|
||||||
|
headers: response.headers,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return new Response(JSON.stringify({ error: error.message }), {
|
||||||
|
status: 502,
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});`;
|
||||||
|
|
||||||
|
export async function POST(request) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const denoToken = body.denoToken?.trim();
|
||||||
|
const orgDomain = body.orgDomain?.trim();
|
||||||
|
const projectName = body.projectName?.trim() || `relay-${Date.now().toString(36)}`;
|
||||||
|
|
||||||
|
if (!orgDomain) {
|
||||||
|
return NextResponse.json({ error: "Organization domain is required" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!denoToken) {
|
||||||
|
return NextResponse.json({ error: "Deno Deploy API token is required" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${denoToken}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAppRes = await fetch(`${DENO_V2_API}/apps`, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify({
|
||||||
|
slug: projectName,
|
||||||
|
labels: { "custom.kind": "9router-relay" },
|
||||||
|
config: {
|
||||||
|
install: "deno install",
|
||||||
|
runtime: {
|
||||||
|
type: "dynamic",
|
||||||
|
entrypoint: "main.ts",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!createAppRes.ok) {
|
||||||
|
const text = await createAppRes.text().catch(() => "");
|
||||||
|
if (createAppRes.status === 409) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: `App "${projectName}" already exists. Choose a different name.` },
|
||||||
|
{ status: 409 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: `Failed to create app (${createAppRes.status}): ${text}` },
|
||||||
|
{ status: createAppRes.status }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = await createAppRes.json();
|
||||||
|
|
||||||
|
const deployRes = await fetch(`${DENO_V2_API}/apps/${app.id}/deploy`, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify({
|
||||||
|
assets: {
|
||||||
|
"main.ts": {
|
||||||
|
kind: "file",
|
||||||
|
content: DENO_RELAY_CODE,
|
||||||
|
encoding: "utf-8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!deployRes.ok) {
|
||||||
|
const text = await deployRes.text().catch(() => "");
|
||||||
|
console.error("Deno Deploy error:", deployRes.status, text);
|
||||||
|
await fetch(`${DENO_V2_API}/apps/${app.id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: { Authorization: `Bearer ${denoToken}` },
|
||||||
|
}).catch(() => {});
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: `Deploy failed (${deployRes.status}): ${text}` },
|
||||||
|
{ status: deployRes.status }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const revision = await deployRes.json();
|
||||||
|
const revisionId = revision.id;
|
||||||
|
|
||||||
|
let status = revision.status;
|
||||||
|
let attempts = 0;
|
||||||
|
const maxAttempts = 30; // 30 * 2s = 60s max
|
||||||
|
while (status === "queued" || status === "building") {
|
||||||
|
if (attempts >= maxAttempts) {
|
||||||
|
throw new Error("Deploy timed out after 60 seconds");
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
const statusRes = await fetch(`${DENO_V2_API}/revisions/${revisionId}`, {
|
||||||
|
headers: { Authorization: `Bearer ${denoToken}` },
|
||||||
|
});
|
||||||
|
if (!statusRes.ok) break;
|
||||||
|
const statusData = await statusRes.json();
|
||||||
|
status = statusData.status;
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status !== "succeeded") {
|
||||||
|
await fetch(`${DENO_V2_API}/apps/${app.id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: { Authorization: `Bearer ${denoToken}` },
|
||||||
|
}).catch(() => {});
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: `Deploy failed with status: ${status}` },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgSlug = orgDomain.split(".")[0];
|
||||||
|
const deployUrl = `https://${projectName}.${orgSlug}.deno.net`;
|
||||||
|
console.log("Deno deployUrl:", deployUrl);
|
||||||
|
|
||||||
|
const proxyPool = await createProxyPool({
|
||||||
|
name: projectName,
|
||||||
|
proxyUrl: deployUrl,
|
||||||
|
type: "deno",
|
||||||
|
noProxy: "",
|
||||||
|
isActive: true,
|
||||||
|
strictProxy: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ proxyPool, deployUrl }, { status: 201 });
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error deploying Deno Deploy relay:", error);
|
||||||
|
return NextResponse.json({ error: error.message || "Deploy failed" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ function toBoolean(value) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VALID_PROXY_TYPES = ["http", "vercel", "cloudflare"];
|
const VALID_PROXY_TYPES = ["http", "vercel", "cloudflare", "deno"];
|
||||||
|
|
||||||
function normalizeProxyPoolInput(body = {}) {
|
function normalizeProxyPoolInput(body = {}) {
|
||||||
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export async function resolveConnectionProxyConfig(
|
|||||||
* Vercel/Cloudflare relay proxies use base URL rewriting
|
* Vercel/Cloudflare relay proxies use base URL rewriting
|
||||||
* instead of HTTP_PROXY environment variables.
|
* instead of HTTP_PROXY environment variables.
|
||||||
*/
|
*/
|
||||||
if (proxyPool.type === "vercel" || proxyPool.type === "cloudflare") {
|
if (proxyPool.type === "vercel" || proxyPool.type === "cloudflare" || proxyPool.type === "deno") {
|
||||||
return {
|
return {
|
||||||
source: proxyPool.type,
|
source: proxyPool.type,
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user