UNPKG

bunway

Version:

Express-style routing toolkit built natively for Bun.

151 lines 5.82 kB
const DEFAULT_METHODS = ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE", "OPTIONS"]; const DEFAULT_MAX_AGE = 600; function ensureHeaderBag(ctx) { const locals = ctx.req.locals; if (!locals.__corsHeaders) { locals.__corsHeaders = {}; } return locals.__corsHeaders; } function setHeader(ctx, bag, name, value) { // Persist for router finalizer + set immediately on ctx.res bag[name] = value; ctx.res.header(name, value); } function appendVary(ctx, bag, value) { const existing = bag["Vary"]; const seen = new Set(); if (existing) { for (const part of existing.split(",")) { const trimmed = part.trim(); if (trimmed) seen.add(trimmed); } } for (const part of value.split(",")) { const trimmed = part.trim(); if (trimmed) seen.add(trimmed); } setHeader(ctx, bag, "Vary", Array.from(seen).join(", ")); } function matchPattern(origin, pattern) { if (typeof pattern === "string") { return origin === pattern; } return pattern.test(origin); } function resolveOrigin(requestOrigin, ctx, originOption, credentials) { if (originOption === "*" || originOption === undefined) { return credentials ? requestOrigin : "*"; } if (originOption === true) { return requestOrigin; } if (typeof originOption === "string") { return matchPattern(requestOrigin, originOption) ? requestOrigin : null; } if (originOption instanceof RegExp) { return originOption.test(requestOrigin) ? requestOrigin : null; } if (Array.isArray(originOption)) { for (const entry of originOption) { if (typeof entry === "string" || entry instanceof RegExp) { if (matchPattern(requestOrigin, entry)) return requestOrigin; } } return null; } if (typeof originOption === "function") { const result = originOption(requestOrigin, ctx); if (result === false || result === null || result === undefined) { return null; } if (result === "*") { return credentials ? requestOrigin : "*"; } return result; } return null; } function formatHeaderList(values) { if (!values || values.length === 0) return null; return values.join(", "); } /** * Bun-native CORS middleware compatible with bunWay's router finalizer. * * @example Allow any localhost origin and forward credentials * ```ts * app.use(cors({ origin: (origin) => origin?.startsWith("http://localhost") ? origin : false, credentials: true })); * ``` * * @example Simple allow-list * ```ts * app.use(cors({ origin: ["https://app.example.com", /\.my-app\.com$/] })); * ``` * * The middleware inspects the incoming Origin/Access-Control headers, * determines whether the request should be allowed, and records all response * headers so the router can apply them even if a handler returns a raw `Response`. */ export function cors(options = {}) { const { origin: originOption = "*", methods = DEFAULT_METHODS, allowedHeaders, exposedHeaders, credentials = false, maxAge = DEFAULT_MAX_AGE, allowPrivateNetwork = false, } = options; const methodsHeader = formatHeaderList(methods) ?? DEFAULT_METHODS.join(", "); const allowedHeadersHeader = formatHeaderList(allowedHeaders); const exposedHeadersHeader = formatHeaderList(exposedHeaders); return async (ctx, next) => { const requestOrigin = ctx.req.headers.get("Origin"); if (!requestOrigin) { await next(); return; } const method = ctx.req.method.toUpperCase(); const isPreflight = method === "OPTIONS" && ctx.req.headers.has("Access-Control-Request-Method"); const resolvedOrigin = resolveOrigin(requestOrigin, ctx, originOption, credentials); if (!resolvedOrigin) { // No headers set; allow the request chain to handle the request. await next(); return; } const bag = ensureHeaderBag(ctx); appendVary(ctx, bag, "Origin"); if (isPreflight) { appendVary(ctx, bag, "Access-Control-Request-Headers"); appendVary(ctx, bag, "Access-Control-Request-Method"); setHeader(ctx, bag, "Access-Control-Allow-Origin", resolvedOrigin); if (credentials) { setHeader(ctx, bag, "Access-Control-Allow-Credentials", "true"); } setHeader(ctx, bag, "Access-Control-Allow-Methods", methodsHeader); if (allowedHeadersHeader) { setHeader(ctx, bag, "Access-Control-Allow-Headers", allowedHeadersHeader); } else { const requestedHeaders = ctx.req.headers.get("Access-Control-Request-Headers"); if (requestedHeaders) { setHeader(ctx, bag, "Access-Control-Allow-Headers", requestedHeaders); } } setHeader(ctx, bag, "Access-Control-Max-Age", String(maxAge)); if (allowPrivateNetwork && ctx.req.headers.get("Access-Control-Request-Private-Network") === "true") { setHeader(ctx, bag, "Access-Control-Allow-Private-Network", "true"); } ctx.res.status(204); ctx.res.send(""); return; } setHeader(ctx, bag, "Access-Control-Allow-Origin", resolvedOrigin); if (credentials) { setHeader(ctx, bag, "Access-Control-Allow-Credentials", "true"); } if (exposedHeadersHeader) { setHeader(ctx, bag, "Access-Control-Expose-Headers", exposedHeadersHeader); } await next(); }; } //# sourceMappingURL=cors.js.map