File size: 4,916 Bytes
88c4c60 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | import { NextResponse } from "next/server";
import { createProxyPool } from "@/models";
// Relay worker source code deployed to Cloudflare
const RELAY_WORKER_CODE = `
export default {
async fetch(request, env, ctx) {
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 newRequestInit = {
method: request.method,
headers: new Headers(request.headers),
};
if (request.method !== "GET" && request.method !== "HEAD") {
newRequestInit.body = request.body;
newRequestInit.duplex = "half";
}
newRequestInit.headers.delete("x-relay-target");
newRequestInit.headers.delete("x-relay-path");
newRequestInit.headers.delete("host");
try {
const response = await fetch(targetUrl, newRequestInit);
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" },
});
}
},
};
`;
// POST /api/proxy-pools/cloudflare-deploy
export async function POST(request) {
try {
const body = await request.json();
const accountId = body.accountId?.trim();
const apiToken = body.apiToken?.trim();
const projectName = body.projectName?.trim() || `relay-${Date.now().toString(36)}`;
if (!accountId || !apiToken) {
return NextResponse.json({ error: "Cloudflare Account ID and API Token are required" }, { status: 400 });
}
// 1. Upload Worker Script
const workerScriptUrl = `https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/scripts/${projectName}`;
// Cloudflare requires multipart/form-data for worker script upload
const formData = new FormData();
formData.append("index.js", new Blob([RELAY_WORKER_CODE], { type: "application/javascript+module" }), "index.js");
formData.append("metadata", new Blob([JSON.stringify({
main_module: "index.js",
compatibility_date: "2024-03-20",
observability: { enabled: true }
})], { type: "application/json" }), "metadata.json");
const uploadRes = await fetch(workerScriptUrl, {
method: "PUT",
headers: {
Authorization: `Bearer ${apiToken}`,
},
body: formData,
});
if (!uploadRes.ok) {
const err = await uploadRes.json().catch(() => ({}));
console.error("Cloudflare upload error:", err);
return NextResponse.json(
{ error: err.errors?.[0]?.message || "Failed to upload Worker to Cloudflare" },
{ status: uploadRes.status }
);
}
// 2. Enable workers.dev subdomain for the script
const enableSubdomainRes = await fetch(`${workerScriptUrl}/subdomain`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ enabled: true }),
});
if (!enableSubdomainRes.ok) {
const err = await enableSubdomainRes.json().catch(() => ({}));
console.error("Cloudflare subdomain enable error:", err);
// We don't fail completely here, just continue
}
// 3. Get the workers.dev subdomain for the account to construct the final URL
let deployUrl = "";
const subdomainRes = await fetch(`https://api.cloudflare.com/client/v4/accounts/${accountId}/workers/subdomain`, {
method: "GET",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
});
if (subdomainRes.ok) {
const subdomainData = await subdomainRes.json();
if (subdomainData.result && subdomainData.result.subdomain) {
deployUrl = `https://${projectName}.${subdomainData.result.subdomain}.workers.dev`;
}
}
if (!deployUrl) {
return NextResponse.json(
{ error: "Worker deployed but failed to retrieve workers.dev subdomain. Make sure you have setup a workers.dev subdomain in Cloudflare Dashboard." },
{ status: 400 }
);
}
// Create proxy pool entry with type cloudflare
const proxyPool = await createProxyPool({
name: projectName,
proxyUrl: deployUrl,
type: "cloudflare",
noProxy: "",
isActive: true,
strictProxy: false,
});
return NextResponse.json({ proxyPool, deployUrl }, { status: 201 });
} catch (error) {
console.log("Error deploying Cloudflare relay:", error);
return NextResponse.json({ error: error.message || "Deploy failed" }, { status: 500 });
}
}
|