GUI-STUDIO commited on
Commit ·
63aa82b
1
Parent(s): 125ba72
Refactor env var access to simplify logic
Browse filesThis commit refactors how environment variables are accessed across
different runtimes (Bun, Node.js, and browser) to simplify the logic. It
also consolidates the checks for `IS_BUN || IS_NODE` where appropriate,
as the access method is the same for both.
Additionally, it removes an unnecessary environment variable
`NODE_TLS_REJECT_UNAUTHORIZED` from the `frontendV3` Vercel
configuration. A new `workers-proxy.ts` file has been added to manage
proxying for Cloudflare Workers.
- src/index.ts +28 -1
- src/proxy/workers-proxy.ts +239 -0
src/index.ts
CHANGED
|
@@ -9,11 +9,17 @@ import { FfmpegRouter } from "./sub-router/ffmpeg";
|
|
| 9 |
import { ShowtimeRouter } from "./sub-router/showtime";
|
| 10 |
import { TestRouter } from "./sub-router/test";
|
| 11 |
import { NS_runtime } from "../../../../packages/lib/env.runtime";
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
const APP_ENV = process.env.APP_ENV;
|
| 14 |
const APP_ORIGIN = process.env.APP_ORIGIN;
|
| 15 |
const PORT = process.env.PORT;
|
| 16 |
|
|
|
|
|
|
|
| 17 |
const app = new Elysia()
|
| 18 |
|
| 19 |
.use(
|
|
@@ -64,10 +70,31 @@ const app = new Elysia()
|
|
| 64 |
.use(FfmpegRouter)
|
| 65 |
.use(TestRouter)
|
| 66 |
|
| 67 |
-
.get("/", () => {
|
| 68 |
return "🦊 Hello Rafiki 🍿";
|
| 69 |
})
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
.listen({
|
| 72 |
port: PORT || 7860,
|
| 73 |
...(NS_runtime.IS_PROD && { hostname: "0.0.0.0" }),
|
|
|
|
| 9 |
import { ShowtimeRouter } from "./sub-router/showtime";
|
| 10 |
import { TestRouter } from "./sub-router/test";
|
| 11 |
import { NS_runtime } from "../../../../packages/lib/env.runtime";
|
| 12 |
+
import { NS_workers_proxy } from "../../../../packages/lib/proxy-server/workers-proxy";
|
| 13 |
+
|
| 14 |
+
const { rewriteWorkersUrl, startWorkersProxy, rewriteWorkersUrl_t2, get_dns } =
|
| 15 |
+
NS_workers_proxy;
|
| 16 |
|
| 17 |
const APP_ENV = process.env.APP_ENV;
|
| 18 |
const APP_ORIGIN = process.env.APP_ORIGIN;
|
| 19 |
const PORT = process.env.PORT;
|
| 20 |
|
| 21 |
+
await startWorkersProxy();
|
| 22 |
+
|
| 23 |
const app = new Elysia()
|
| 24 |
|
| 25 |
.use(
|
|
|
|
| 70 |
.use(FfmpegRouter)
|
| 71 |
.use(TestRouter)
|
| 72 |
|
| 73 |
+
.get("/", async ({ request }) => {
|
| 74 |
return "🦊 Hello Rafiki 🍿";
|
| 75 |
})
|
| 76 |
|
| 77 |
+
.get("/test/p", async ({ request }) => {
|
| 78 |
+
const originalUrl =
|
| 79 |
+
"https://cool.tylerfisher55.workers.dev/afc7d47f/uz5gyMPGU9TVujalv1-l0gCq1t5YoLPmJOs0BK8vPdXqyusELznNCnUjCjs93MmANIYp__VYV9NVMM33xaH8-hxFDOQjzy6xGM8xf6yfw1YOmJhdwv2R3fFOXPVk90JFUhfo-oz1cLaYBHPnU0wPrb84UsIVRnunq3UKDSpFMLVBnEy7VJtuHl7T90GpI0EcY_xtuRtQguJ7pQqdKgGVJRP7_urSkYuXf-zAySLgY6isRw-vKYh1EnOLybifGW5Q8AT-r-BlvwNe4vDMjJ3blHFeAsSS2Z3XMfOdM0dQbJwO7o4igwfpIVILbI2YkKuRzIbaWTMXAe7f9vKUJpS5cXEG5UB3EwOm_-yobxVKvrPZqcdrG-Qteh16fTl9Ll7PiYwlA-Ebc2S0QlEWRAXsB0IZd3ixrCxSqSfHZwF6p38FGC0EeNUqW_enbS8BA_kGQhiA9q9Z4P2yxF5CN5-WBTXw6O8UMygbUNhoi-BWnk02DRu0oeyEoE2OGVrN-zRP2KNGOejNL-BbmcLd9yB0qP1Z1RF8wszuFx8x6iQUuIXCXUSGzxZAQie3dELhVWFKSj__CqTSgk8adDCyd8mYLnHzh1jFpSlDDOwF65blF6rmWcYZmgfHcSIe9mIgpkRE0zPF5J_AcvKdYSoILfKj2kzpaF_yI8DceEqCWsDojyzMQi7YO02EMx8MdmtqG9BnHtto7tEiVWZnOQJyc6BinQ.m3u8";
|
| 80 |
+
|
| 81 |
+
await get_dns({
|
| 82 |
+
target: originalUrl,
|
| 83 |
+
method: "GET",
|
| 84 |
+
headers: {
|
| 85 |
+
Referer: "https://111movies.net/"
|
| 86 |
+
}
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
const foo = rewriteWorkersUrl_t2(originalUrl);
|
| 90 |
+
|
| 91 |
+
const res = await fetch(foo, {
|
| 92 |
+
method: "GET"
|
| 93 |
+
});
|
| 94 |
+
|
| 95 |
+
return res;
|
| 96 |
+
})
|
| 97 |
+
|
| 98 |
.listen({
|
| 99 |
port: PORT || 7860,
|
| 100 |
...(NS_runtime.IS_PROD && { hostname: "0.0.0.0" }),
|
src/proxy/workers-proxy.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as https from "node:https";
|
| 2 |
+
import * as http from "node:http";
|
| 3 |
+
|
| 4 |
+
export const PROXY_PORT = 18080;
|
| 5 |
+
let proxyServer: http.Server | null = null;
|
| 6 |
+
|
| 7 |
+
const REPO_ORIGIN = process.env.REPO_ORIGIN;
|
| 8 |
+
|
| 9 |
+
// Registry of active proxy targets — keyed by servername
|
| 10 |
+
const targetRegistry = new Map<
|
| 11 |
+
string,
|
| 12 |
+
{
|
| 13 |
+
hostname: string;
|
| 14 |
+
port: number;
|
| 15 |
+
servername: string;
|
| 16 |
+
headers: Record<string, string>;
|
| 17 |
+
path: string;
|
| 18 |
+
method: string;
|
| 19 |
+
}
|
| 20 |
+
>();
|
| 21 |
+
|
| 22 |
+
export function registerProxyTarget(config: {
|
| 23 |
+
hostname: string;
|
| 24 |
+
port: number;
|
| 25 |
+
servername: string;
|
| 26 |
+
headers: Record<string, string>;
|
| 27 |
+
path: string;
|
| 28 |
+
method: string;
|
| 29 |
+
}) {
|
| 30 |
+
targetRegistry.set(config.servername, config);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
export async function get_dns(arg0: {
|
| 34 |
+
target: string;
|
| 35 |
+
method: string;
|
| 36 |
+
headers: Record<string, string>;
|
| 37 |
+
}) {
|
| 38 |
+
if (!REPO_ORIGIN) return;
|
| 39 |
+
try {
|
| 40 |
+
const { target, headers, method } = arg0;
|
| 41 |
+
|
| 42 |
+
const res = await fetch(`${REPO_ORIGIN}/dns/resolve`, {
|
| 43 |
+
method: "POST",
|
| 44 |
+
headers: {
|
| 45 |
+
"Content-Type": "application/json"
|
| 46 |
+
},
|
| 47 |
+
body: JSON.stringify({ target, method, headers })
|
| 48 |
+
});
|
| 49 |
+
const data = await res.json();
|
| 50 |
+
|
| 51 |
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
| 52 |
+
registerProxyTarget({
|
| 53 |
+
...data
|
| 54 |
+
});
|
| 55 |
+
|
| 56 |
+
return data;
|
| 57 |
+
}
|
| 58 |
+
} catch {}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
export function rewriteWorkersUrl(originalUrl: string): string {
|
| 62 |
+
try {
|
| 63 |
+
const u = new URL(originalUrl);
|
| 64 |
+
if (u.hostname.endsWith(".workers.dev")) {
|
| 65 |
+
return `http://127.0.0.1:${PROXY_PORT}/__proxy__/${u.hostname}${u.pathname}${u.search}`;
|
| 66 |
+
}
|
| 67 |
+
} catch {}
|
| 68 |
+
return originalUrl;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
export function rewriteWorkersUrl_t2(originalUrl: string): string {
|
| 72 |
+
try {
|
| 73 |
+
const is_http = originalUrl.startsWith("http");
|
| 74 |
+
|
| 75 |
+
if (!is_http) return originalUrl;
|
| 76 |
+
|
| 77 |
+
const start_http = originalUrl.startsWith("https://")
|
| 78 |
+
? "https://"
|
| 79 |
+
: "http://";
|
| 80 |
+
const rest = originalUrl.replace(start_http, "");
|
| 81 |
+
|
| 82 |
+
if (rest) return `http://127.0.0.1:${PROXY_PORT}/__proxy__/${rest}`;
|
| 83 |
+
|
| 84 |
+
return originalUrl;
|
| 85 |
+
} catch {}
|
| 86 |
+
return originalUrl;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
function isM3u8(headers: http.IncomingHttpHeaders): boolean {
|
| 90 |
+
const ct = headers["content-type"] ?? "";
|
| 91 |
+
// Also check path-based detection as some servers return wrong content-type
|
| 92 |
+
return ct.includes("mpegurl") || ct.includes("x-mpegurl");
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
function rewriteM3u8Body(body: string): string {
|
| 96 |
+
console.log({ body });
|
| 97 |
+
return body.replace(
|
| 98 |
+
/https?:\/\/([a-zA-Z0-9._-]+\.workers\.dev)(:[0-9]+)?([^\s"'\n]*)/g,
|
| 99 |
+
(_, host, _port, rest) =>
|
| 100 |
+
`http://127.0.0.1:${PROXY_PORT}/__proxy__/${host}${rest}`
|
| 101 |
+
);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
export async function startWorkersProxy(): Promise<void> {
|
| 105 |
+
if (proxyServer) return Promise.resolve(); // already running
|
| 106 |
+
|
| 107 |
+
return new Promise((resolve, reject) => {
|
| 108 |
+
proxyServer = http.createServer((req, res) => {
|
| 109 |
+
// Parse target host from /__proxy__/<host>/path or Host header
|
| 110 |
+
let targetHost: string;
|
| 111 |
+
let targetPath: string;
|
| 112 |
+
|
| 113 |
+
const proxyPrefixMatch = req.url?.match(/^\/__proxy__\/([^/]+)(\/.*)?$/);
|
| 114 |
+
|
| 115 |
+
if (proxyPrefixMatch) {
|
| 116 |
+
targetHost = proxyPrefixMatch[1];
|
| 117 |
+
targetPath = proxyPrefixMatch[2] ?? "/";
|
| 118 |
+
// Preserve query string
|
| 119 |
+
const qIndex = (req.url ?? "").indexOf(
|
| 120 |
+
"?",
|
| 121 |
+
`/__proxy__/${targetHost}`.length
|
| 122 |
+
);
|
| 123 |
+
if (qIndex !== -1) {
|
| 124 |
+
targetPath = proxyPrefixMatch[2]?.split("?")[0] ?? "/";
|
| 125 |
+
targetPath += req.url!.slice(qIndex);
|
| 126 |
+
}
|
| 127 |
+
} else {
|
| 128 |
+
targetHost = (req.headers["host"] ?? "").split(":")[0];
|
| 129 |
+
targetPath = req.url ?? "/";
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
if (!targetHost.endsWith(".workers.dev")) {
|
| 133 |
+
res.writeHead(400);
|
| 134 |
+
res.end(`[workers-proxy] unexpected host: ${targetHost}`);
|
| 135 |
+
return;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
// Look up registered target or fall back to Cloudflare Anycast
|
| 139 |
+
const registered = (() => {
|
| 140 |
+
const _ = targetRegistry.get(targetHost);
|
| 141 |
+
|
| 142 |
+
return _;
|
| 143 |
+
})();
|
| 144 |
+
|
| 145 |
+
if (!registered) {
|
| 146 |
+
console.error(
|
| 147 |
+
`[workers-proxy] no registered target for: ${targetHost}`
|
| 148 |
+
);
|
| 149 |
+
res.writeHead(502);
|
| 150 |
+
res.end(`[workers-proxy] no registered target for: ${targetHost}`);
|
| 151 |
+
return;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
const targetIp = registered.hostname;
|
| 155 |
+
const extraHeaders = registered.headers ?? {};
|
| 156 |
+
|
| 157 |
+
const options: https.RequestOptions = {
|
| 158 |
+
hostname: targetIp,
|
| 159 |
+
port: registered.port || 443,
|
| 160 |
+
path: registered.path || targetPath,
|
| 161 |
+
method: registered.method || req.method, // registered.method
|
| 162 |
+
servername: registered.servername || targetHost, // SNI
|
| 163 |
+
rejectUnauthorized: false,
|
| 164 |
+
headers: {
|
| 165 |
+
// ...req.headers,
|
| 166 |
+
...extraHeaders,
|
| 167 |
+
Host: targetHost,
|
| 168 |
+
host: targetHost
|
| 169 |
+
}
|
| 170 |
+
};
|
| 171 |
+
|
| 172 |
+
console.log(
|
| 173 |
+
`[workers-proxy] ${req.method} ${targetHost}${targetPath} → ${targetIp}`
|
| 174 |
+
);
|
| 175 |
+
|
| 176 |
+
const proxyReq = https.request(options, (proxyRes) => {
|
| 177 |
+
const isPlaylist =
|
| 178 |
+
isM3u8(proxyRes.headers) ||
|
| 179 |
+
targetPath.includes(".m3u8") ||
|
| 180 |
+
targetPath.includes(".m3u");
|
| 181 |
+
|
| 182 |
+
if (isPlaylist) {
|
| 183 |
+
const chunks: Buffer[] = [];
|
| 184 |
+
proxyRes.on("data", (chunk) => chunks.push(chunk));
|
| 185 |
+
proxyRes.on("end", () => {
|
| 186 |
+
const original = Buffer.concat(chunks).toString("utf8");
|
| 187 |
+
const rewritten = rewriteM3u8Body(original);
|
| 188 |
+
|
| 189 |
+
console.log({ rewritten });
|
| 190 |
+
|
| 191 |
+
const responseHeaders: Record<string, any> = {
|
| 192 |
+
...proxyRes.headers,
|
| 193 |
+
"content-length": Buffer.byteLength(rewritten).toString(),
|
| 194 |
+
"content-encoding": undefined // decoded already
|
| 195 |
+
};
|
| 196 |
+
// Remove undefined keys
|
| 197 |
+
Object.keys(responseHeaders).forEach(
|
| 198 |
+
(k) =>
|
| 199 |
+
responseHeaders[k] === undefined && delete responseHeaders[k]
|
| 200 |
+
);
|
| 201 |
+
|
| 202 |
+
res.writeHead(proxyRes.statusCode ?? 502, responseHeaders);
|
| 203 |
+
res.end(rewritten);
|
| 204 |
+
});
|
| 205 |
+
} else {
|
| 206 |
+
// Segments — stream straight through, no buffering
|
| 207 |
+
res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
|
| 208 |
+
proxyRes.pipe(res);
|
| 209 |
+
}
|
| 210 |
+
});
|
| 211 |
+
|
| 212 |
+
proxyReq.on("error", (err) => {
|
| 213 |
+
console.error(`[workers-proxy] error for ${targetHost}:`, err.message);
|
| 214 |
+
if (!res.headersSent) {
|
| 215 |
+
res.writeHead(502);
|
| 216 |
+
res.end("Proxy error: " + err.message);
|
| 217 |
+
}
|
| 218 |
+
});
|
| 219 |
+
|
| 220 |
+
req.pipe(proxyReq);
|
| 221 |
+
});
|
| 222 |
+
|
| 223 |
+
proxyServer.listen(PROXY_PORT, "127.0.0.1", () => {
|
| 224 |
+
console.log(
|
| 225 |
+
`[workers-proxy] listening on http://127.0.0.1:${PROXY_PORT}`
|
| 226 |
+
);
|
| 227 |
+
resolve();
|
| 228 |
+
});
|
| 229 |
+
|
| 230 |
+
proxyServer.on("error", reject);
|
| 231 |
+
});
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
export function stopWorkersProxy(): Promise<void> {
|
| 235 |
+
return new Promise((resolve) => {
|
| 236 |
+
if (proxyServer) proxyServer.close(() => resolve());
|
| 237 |
+
else resolve();
|
| 238 |
+
});
|
| 239 |
+
}
|