Download app/api/proxy/[...path]/route.ts from Etiya/d2l-ui: direct link, hf CLI and curl.
- Browser
- Download file 3.54 kB
-
https://huggingface.co/spaces/Etiya/d2l-ui/resolve/main/app/api/proxy/%5B...path%5D/route.ts
- Command line
-
hf download 'hf://spaces/Etiya/d2l-ui/app/api/proxy/[...path]/route.ts'
-
curl -L -o route.ts https://huggingface.co/spaces/Etiya/d2l-ui/resolve/main/app/api/proxy/%5B...path%5D/route.ts
3.54 kB
| /** | |
| * Server-side proxy to https://etiya-d2l-api.hf.space. | |
| * | |
| * Browser → /api/proxy/<path> (NO auth header in browser request) | |
| * ↓ | |
| * Next.js server adds Authorization: Bearer ${HF_TOKEN} | |
| * ↓ | |
| * Forwards to D2L_API_URL/<path> | |
| * | |
| * Why a proxy and not NEXT_PUBLIC_HF_TOKEN? | |
| * - NEXT_PUBLIC_* values are inlined into the client bundle. | |
| * Anyone viewing the page source can read them. | |
| * - Server-only env vars (no NEXT_PUBLIC_) stay on the server. | |
| * The browser cannot read them. This is the only safe pattern | |
| * for Bearer tokens. | |
| */ | |
| import { NextRequest, NextResponse } from "next/server"; | |
| const API_URL = process.env.D2L_API_URL || "https://etiya-d2l-api.hf.space"; | |
| const TOKEN = process.env.HF_TOKEN; | |
| export const dynamic = "force-dynamic"; | |
| export const runtime = "nodejs"; | |
| async function handle(req: NextRequest, params: { path: string[] }) { | |
| if (!TOKEN) { | |
| return NextResponse.json( | |
| { | |
| error: | |
| "HF_TOKEN env var not set on server. Copy .env.example to .env.local and add your token.", | |
| }, | |
| { status: 500 } | |
| ); | |
| } | |
| const path = (params.path ?? []).join("/"); | |
| const search = req.nextUrl.search; // includes leading "?" | |
| const upstreamUrl = `${API_URL}/${path}${search}`; | |
| const upstreamHeaders: Record<string, string> = { | |
| Authorization: `Bearer ${TOKEN}`, | |
| // Forward client's Accept so SSE requests propagate; default to JSON. | |
| Accept: req.headers.get("accept") || "application/json", | |
| }; | |
| let body: BodyInit | null = null; | |
| if (req.method !== "GET" && req.method !== "HEAD") { | |
| const contentType = req.headers.get("content-type") || "application/json"; | |
| upstreamHeaders["Content-Type"] = contentType; | |
| body = await req.text(); | |
| } | |
| let resp: Response; | |
| try { | |
| resp = await fetch(upstreamUrl, { | |
| method: req.method, | |
| headers: upstreamHeaders, | |
| body, | |
| cache: "no-store", | |
| }); | |
| } catch (e: unknown) { | |
| return NextResponse.json( | |
| { | |
| error: "upstream_unreachable", | |
| message: e instanceof Error ? e.message : String(e), | |
| upstream: upstreamUrl, | |
| }, | |
| { status: 502 } | |
| ); | |
| } | |
| // Pass the upstream body through as a stream so SSE (text/event-stream) | |
| // reaches the browser unbuffered. Buffering with `await resp.text()` would | |
| // collapse streaming into a single chunk and defeat token-by-token render. | |
| const contentType = resp.headers.get("content-type") || "application/json"; | |
| const headers: Record<string, string> = { | |
| "content-type": contentType, | |
| "cache-control": resp.headers.get("cache-control") ?? "no-cache, no-transform", | |
| }; | |
| if (contentType.includes("text/event-stream")) { | |
| headers["x-accel-buffering"] = "no"; | |
| headers["connection"] = "keep-alive"; | |
| } | |
| return new NextResponse(resp.body, { | |
| status: resp.status, | |
| headers, | |
| }); | |
| } | |
| // Next.js 15+ — `params` is a Promise that must be awaited inside the route. | |
| type RouteCtx = { params: Promise<{ path: string[] }> }; | |
| export async function GET(req: NextRequest, ctx: RouteCtx) { | |
| return handle(req, await ctx.params); | |
| } | |
| export async function POST(req: NextRequest, ctx: RouteCtx) { | |
| return handle(req, await ctx.params); | |
| } | |
| export async function PUT(req: NextRequest, ctx: RouteCtx) { | |
| return handle(req, await ctx.params); | |
| } | |
| export async function DELETE(req: NextRequest, ctx: RouteCtx) { | |
| return handle(req, await ctx.params); | |
| } | |
| export async function PATCH(req: NextRequest, ctx: RouteCtx) { | |
| return handle(req, await ctx.params); | |
| } | |