Skip to content

Commit

Permalink
Merge branch 'main' into upgrade-jsforce-v3
Browse files Browse the repository at this point in the history
  • Loading branch information
joeauyeung authored Dec 27, 2024
2 parents 852ceec + e2f45bd commit e8e6c40
Show file tree
Hide file tree
Showing 131 changed files with 2,553 additions and 1,001 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ NEXT_PUBLIC_POSTHOG_HOST=
PLAIN_API_KEY=
PLAIN_API_URL=https://api.plain.com/v1
PLAIN_HMAC_SECRET_KEY=
PLAIN_CHAT_ID=
PLAIN_CHAT_HMAC_SECRET_KEY=

# Zendesk Config
NEXT_PUBLIC_ZENDESK_KEY=
Expand Down
2 changes: 2 additions & 0 deletions .yarn/versions/aecb4352.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
undecided:
- "@calcom/prisma"
2 changes: 2 additions & 0 deletions .yarn/versions/c92a78d0.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
undecided:
- calcom-monorepo
247 changes: 247 additions & 0 deletions apps/web/app/WithEmbedSSR.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse, Redirect } from "next";
import { redirect, notFound } from "next/navigation";
import { createMocks } from "node-mocks-http";
import { describe, expect, it, vi } from "vitest";

import withEmbedSsrAppDir from "./WithEmbedSSR";

export type CustomNextApiRequest = NextApiRequest & Request;
export type CustomNextApiResponse = NextApiResponse & Response;

export function createMockNextJsRequest(...args: Parameters<typeof createMocks>) {
return createMocks<CustomNextApiRequest, CustomNextApiResponse>(...args);
}

vi.mock("next/navigation", () => ({
redirect: vi.fn(),
notFound: vi.fn(),
}));

function getServerSidePropsFnGenerator(
config:
| { redirectUrl: string }
| { props: Record<string, unknown> }
| {
notFound: true;
}
) {
if ("redirectUrl" in config)
return async () => {
return {
redirect: {
permanent: false,
destination: config.redirectUrl,
} satisfies Redirect,
};
};

if ("props" in config)
return async () => {
return {
props: config.props,
};
};

if ("notFound" in config)
return async () => {
return {
notFound: true as const,
};
};

throw new Error("Invalid config");
}

interface ServerSidePropsContext {
embedRelatedParams?: Record<string, string>;
}

function getServerSidePropsContextArg({ embedRelatedParams = {} }: ServerSidePropsContext) {
const { req, res } = createMockNextJsRequest();
return {
req,
res,
query: {
...embedRelatedParams,
},
resolvedUrl: "/MOCKED_RESOLVED_URL",
};
}

describe("withEmbedSsrAppDir", () => {
describe("when gSSP returns redirect", () => {
describe("when redirect destination is relative", () => {
it("should redirect with layout and embed params from the current query", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "/reschedule",
})
);

await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "namespace1",
},
})
).catch(() => {});

expect(redirect).toHaveBeenCalledWith("/reschedule/embed?layout=week_view&embed=namespace1");
});

it("should preserve existing query params in redirect URL", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "/reschedule?redirectParam=1",
})
);

await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "namespace1",
},
})
).catch(() => {});

expect(redirect).toHaveBeenCalledWith(
"/reschedule/embed?redirectParam=1&layout=week_view&embed=namespace1"
);
});

it("should handle empty embed namespace", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "/reschedule?redirectParam=1",
})
);

await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "",
},
})
).catch(() => {});

expect(redirect).toHaveBeenCalledWith("/reschedule/embed?redirectParam=1&layout=week_view&embed=");
});
});

describe("when redirect destination is absolute", () => {
it("should handle HTTPS URLs", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "https://calcom.cal.local/owner",
})
);

await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "namespace1",
},
})
).catch(() => {});

expect(redirect).toHaveBeenCalledWith(
"https://calcom.cal.local/owner/embed?layout=week_view&embed=namespace1"
);
});

it("should handle HTTP URLs", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "http://calcom.cal.local/owner",
})
);

await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "namespace1",
},
})
).catch(() => {});

expect(redirect).toHaveBeenCalledWith(
"http://calcom.cal.local/owner/embed?layout=week_view&embed=namespace1"
);
});

it("should treat URLs without protocol as relative", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
redirectUrl: "calcom.cal.local/owner",
})
);

await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "namespace1",
},
})
).catch(() => {});

expect(redirect).toHaveBeenCalledWith(
"/calcom.cal.local/owner/embed?layout=week_view&embed=namespace1"
);
});
});
});

describe("when gSSP returns props", () => {
it("should add isEmbed=true prop", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
props: {
prop1: "value1",
},
})
);

const ret = await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "",
},
})
);

expect(ret).toEqual({
prop1: "value1",
isEmbed: true,
});
});
});

describe("when gSSP returns notFound", () => {
it("should throw notFound", async () => {
const withEmbedGetSsr = withEmbedSsrAppDir(
getServerSidePropsFnGenerator({
notFound: true,
})
);

await withEmbedGetSsr(
getServerSidePropsContextArg({
embedRelatedParams: {
layout: "week_view",
embed: "",
},
})
).catch(() => {});

expect(notFound).toHaveBeenCalled();
});
});
});
80 changes: 38 additions & 42 deletions apps/web/app/WithEmbedSSR.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type { GetServerSidePropsContext } from "next";
import { isNotFoundError } from "next/dist/client/components/not-found";
import { getURLFromRedirectError, isRedirectError } from "next/dist/client/components/redirect";
import type { GetServerSideProps, GetServerSidePropsContext } from "next";
import { notFound, redirect } from "next/navigation";

import { WebAppURL } from "@calcom/lib/WebAppURL";
Expand All @@ -9,48 +7,46 @@ export type EmbedProps = {
isEmbed?: boolean;
};

export default function withEmbedSsrAppDir<T extends Record<string, any>>(
getData: (context: GetServerSidePropsContext) => Promise<T>
) {
return async (context: GetServerSidePropsContext): Promise<T> => {
const withEmbedSsrAppDir =
<T extends Record<string, any>>(getServerSideProps: GetServerSideProps<T>) =>
async (context: GetServerSidePropsContext): Promise<T> => {
const { embed, layout } = context.query;
try {
const props = await getData(context);

return {
...props,
isEmbed: true,
};
} catch (e) {
if (isRedirectError(e)) {
const destinationUrl = getURLFromRedirectError(e);
let urlPrefix = "";

// Get the URL parsed from URL so that we can reliably read pathname and searchParams from it.
const destinationUrlObj = new WebAppURL(destinationUrl);

// If it's a complete URL, use the origin as the prefix to ensure we redirect to the same domain.
if (destinationUrl.search(/^(http:|https:).*/) !== -1) {
urlPrefix = destinationUrlObj.origin;
} else {
// Don't use any prefix for relative URLs to ensure we stay on the same domain
urlPrefix = "";
}

const destinationQueryStr = destinationUrlObj.searchParams.toString();
// Make sure that redirect happens to /embed page and pass on embed query param as is for preserving Cal JS API namespace
const newDestinationUrl = `${urlPrefix}${destinationUrlObj.pathname}/embed?${
destinationQueryStr ? `${destinationQueryStr}&` : ""
}layout=${layout}&embed=${embed}`;

redirect(newDestinationUrl);
}
const ssrResponse = await getServerSideProps(context);

if ("redirect" in ssrResponse) {
const destinationUrl = ssrResponse.redirect.destination;
let urlPrefix = "";

if (isNotFoundError(e)) {
notFound();
// Get the URL parsed from URL so that we can reliably read pathname and searchParams from it.
const destinationUrlObj = new WebAppURL(ssrResponse.redirect.destination);

// If it's a complete URL, use the origin as the prefix to ensure we redirect to the same domain.
if (destinationUrl.search(/^(http:|https:).*/) !== -1) {
urlPrefix = destinationUrlObj.origin;
} else {
// Don't use any prefix for relative URLs to ensure we stay on the same domain
urlPrefix = "";
}

throw e;
const destinationQueryStr = destinationUrlObj.searchParams.toString();
// Make sure that redirect happens to /embed page and pass on embed query param as is for preserving Cal JS API namespace
const newDestinationUrl = `${urlPrefix}${destinationUrlObj.pathname}/embed?${
destinationQueryStr ? `${destinationQueryStr}&` : ""
}layout=${layout}&embed=${embed}`;
redirect(newDestinationUrl);
}

if ("notFound" in ssrResponse) {
notFound();
}

return {
...ssrResponse.props,
...("trpcState" in ssrResponse.props && {
dehydratedState: ssrResponse.props.trpcState,
}),
isEmbed: true,
};
};
}

export default withEmbedSsrAppDir;
Loading

0 comments on commit e8e6c40

Please sign in to comment.