feat: return `x-vercel-ip-timezone` in headers (#6849)
* feat: add trpc to matcher and pass vercel timezone header * feat: pass request to context * feat: return timezone in header * refactor: split context * fix: remove tsignore comment * Update [trpc].ts --------- Co-authored-by: zomars <zomars@me.com>pull/6849/merge
parent
05c33bd94b
commit
f0db97bbb1
|
@ -59,6 +59,16 @@ const middleware: NextMiddleware = async (req) => {
|
|||
return NextResponse.rewrite(url);
|
||||
}
|
||||
|
||||
if (url.pathname.startsWith("/api/trpc/")) {
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
requestHeaders.set("x-cal-timezone", req.headers.get("x-vercel-ip-timezone") ?? "");
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (url.pathname.startsWith("/auth/login")) {
|
||||
const moreHeaders = new Headers(req.headers);
|
||||
// Use this header to actually enforce CSP, otherwise it is running in Report Only mode on all pages.
|
||||
|
@ -79,6 +89,7 @@ export const config = {
|
|||
"/api/auth/:path*",
|
||||
"/apps/routing_forms/:path*",
|
||||
"/:path*/embed",
|
||||
"/api/trpc/:path*",
|
||||
"/auth/login",
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
/**
|
||||
* This file contains tRPC's HTTP response handler
|
||||
*/
|
||||
import { z } from "zod";
|
||||
|
||||
import * as trpcNext from "@calcom/trpc/server/adapters/next";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { appRouter } from "@calcom/trpc/server/routers/_app";
|
||||
|
@ -35,29 +37,35 @@ export default trpcNext.createNextApiHandler({
|
|||
const TWO_HOURS_IN_SECONDS = 60 * 60 * 2;
|
||||
|
||||
// Our response to indicate no caching
|
||||
const noCacheResponse = {};
|
||||
const defaultHeaders: Record<"headers", Record<string, string>> = {
|
||||
headers: {},
|
||||
};
|
||||
|
||||
if (!!ctx?.req) {
|
||||
const timezone = z.string().safeParse(ctx.req.headers["x-vercel-ip-timezone"]);
|
||||
if (timezone.success) defaultHeaders.headers["x-cal-timezone"] = timezone.data;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore ctx.req is available for SSR but not SSG
|
||||
const isSSR = !!ctx?.req;
|
||||
if (isSSR) {
|
||||
return noCacheResponse;
|
||||
if (!!ctx?.req) {
|
||||
return defaultHeaders;
|
||||
}
|
||||
|
||||
// No caching if we have a non-public path
|
||||
// Assuming we have all our public routes in `viewer.public`
|
||||
if (!paths || !paths.every((path) => path.startsWith("viewer.public."))) {
|
||||
return noCacheResponse;
|
||||
return defaultHeaders;
|
||||
}
|
||||
|
||||
// No caching if we have any procedures errored
|
||||
if (errors.length !== 0) {
|
||||
return noCacheResponse;
|
||||
return defaultHeaders;
|
||||
}
|
||||
|
||||
// Never cache non-queries (aka mutations)
|
||||
if (type !== "query") {
|
||||
return noCacheResponse;
|
||||
return defaultHeaders;
|
||||
}
|
||||
|
||||
// cache request for 1 day + revalidate once every 5 seconds
|
||||
|
@ -77,11 +85,9 @@ export default trpcNext.createNextApiHandler({
|
|||
// Get the cache value of the matching element, if any
|
||||
if (matchedPath) cacheValue = cacheRules[matchedPath];
|
||||
|
||||
if (cacheValue) defaultHeaders.headers["Cache-Control"] = cacheValue;
|
||||
|
||||
// Finally we respond with our resolved cache value
|
||||
return {
|
||||
headers: {
|
||||
"Cache-Control": cacheValue,
|
||||
},
|
||||
};
|
||||
return defaultHeaders;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -101,24 +101,47 @@ async function getUserFromSession({
|
|||
};
|
||||
}
|
||||
|
||||
type CreateInnerContextOptions = {
|
||||
session: Session | null;
|
||||
locale: string;
|
||||
user: Awaited<ReturnType<typeof getUserFromSession>>;
|
||||
i18n: Awaited<ReturnType<typeof serverSideTranslations>>;
|
||||
} & Partial<CreateContextOptions>;
|
||||
|
||||
/**
|
||||
* Inner context. Will always be available in your procedures, in contrast to the outer context.
|
||||
*
|
||||
* Also useful for:
|
||||
* - testing, so you don't have to mock Next.js' `req`/`res`
|
||||
* - tRPC's `createSSGHelpers` where we don't have `req`/`res`
|
||||
*
|
||||
* @see https://trpc.io/docs/context#inner-and-outer-context
|
||||
*/
|
||||
export async function createContextInner(opts: CreateInnerContextOptions) {
|
||||
return {
|
||||
prisma,
|
||||
...opts,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates context for an incoming request
|
||||
* @link https://trpc.io/docs/context
|
||||
*/
|
||||
export const createContext = async ({ req }: CreateContextOptions, sessionGetter = getSession) => {
|
||||
export const createContext = async ({ req, res }: CreateContextOptions, sessionGetter = getSession) => {
|
||||
// for API-response caching see https://trpc.io/docs/caching
|
||||
const session = await sessionGetter({ req });
|
||||
|
||||
const user = await getUserFromSession({ session, req });
|
||||
const locale = user?.locale ?? getLocaleFromHeaders(req);
|
||||
const i18n = await serverSideTranslations(locale, ["common", "vital"]);
|
||||
|
||||
const contextInner = await createContextInner({ session, i18n, locale, user });
|
||||
return {
|
||||
i18n,
|
||||
prisma,
|
||||
session,
|
||||
user,
|
||||
locale,
|
||||
...contextInner,
|
||||
req,
|
||||
res,
|
||||
};
|
||||
};
|
||||
|
||||
export type Context = trpc.inferAsyncReturnType<typeof createContext>;
|
||||
export type Context = trpc.inferAsyncReturnType<typeof createContextInner>;
|
||||
|
|
Loading…
Reference in New Issue