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
Nafees Nazik 2023-02-09 06:42:45 +05:30 committed by GitHub
parent 05c33bd94b
commit f0db97bbb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 19 deletions

View File

@ -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",
],
};

View File

@ -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;
},
});

View File

@ -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>;