From 344241cfbfc4d9a96a40d42888379f6a35c9f186 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Fri, 16 Jun 2023 16:01:57 +0530 Subject: [PATCH] fix: New Booker Rewrites (#9557) * Solve the problem using Next.config.js only * Debug * Avoid infinite rewritees * Apply suggestions from code review * Apply suggestions from code review * Remove stale comments * Update packages/embeds/embed-core/src/embed-iframe.ts --- apps/web/middleware.ts | 18 --- apps/web/next.config.js | 130 ++++++++++-------- .../embeds/embed-core/embed-iframe-init.ts | 7 +- .../embeds/embed-core/src/embed-iframe.ts | 3 - packages/embeds/embed-core/src/sdk-event.ts | 5 +- 5 files changed, 86 insertions(+), 77 deletions(-) diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 0570b43643..30cfe7eb4c 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -12,21 +12,10 @@ const middleware: NextMiddleware = async (req) => { const url = req.nextUrl; const requestHeaders = new Headers(req.headers); const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(req.headers.get("host") ?? ""); - const isEmbedRequest = typeof url.searchParams.get("embed") === "string"; - /** * We are using env variable to toggle new-booker because using flags would be an unnecessary delay for booking pages * Also, we can't easily identify the booker page requests here(to just fetch the flags for those requests) */ - // Enable New Booker for All but embed Requests - if (process.env.NEW_BOOKER_ENABLED_FOR_NON_EMBED === "1" && !isEmbedRequest) { - requestHeaders.set("new-booker-enabled", "1"); - } - - // Enable New Booker for Embed Requests - if (process.env.NEW_BOOKER_ENABLED_FOR_EMBED === "1" && isEmbedRequest) { - requestHeaders.set("new-booker-enabled", "1"); - } // Make sure we are in the presence of an organization if (isValidOrgDomain && url.pathname === "/") { @@ -79,13 +68,6 @@ const middleware: NextMiddleware = async (req) => { } } - // Ensure that embed query param is there in when /embed is added. - // query param is the way in which client side code knows that it is in embed mode. - if (url.pathname.endsWith("/embed") && typeof url.searchParams.get("embed") !== "string") { - url.searchParams.set("embed", ""); - return NextResponse.redirect(url); - } - // Don't 404 old routing_forms links if (url.pathname.startsWith("/apps/routing_forms")) { url.pathname = url.pathname.replace("/apps/routing_forms", "/apps/routing-forms"); diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 3ccc300bf3..b86bd43e13 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -183,7 +183,12 @@ const nextConfig = { return config; }, async rewrites() { - return [ + // .* matches / as well(Note: *(i.e wildcard) doesn't match / but .*(i.e. RegExp) does) + // It would match /free/30min but not /bookings/upcoming because 'bookings' is an item in pages + // It would also not match /free/30min/embed because we are ensuring just two slashes + const userTypeRouteRegExp = `/:user((?!${pages.join("|")})[^/]*)/:type`; + + let rewrites = [ { source: "/org/:slug", destination: "/team/:slug", @@ -219,68 +224,85 @@ const nextConfig = { source: "/cancel/:path*", destination: "/booking/:path*", }, + // Keep cookie based booker enabled just in case we disable new-booker globally + ...[ + { + source: userTypeRouteRegExp, + destination: "/new-booker/:user/:type", + has: [{ type: "cookie", key: "new-booker-enabled" }], + }, + { + source: "/team/:slug/:type", + destination: "/new-booker/team/:slug/:type", + has: [{ type: "cookie", key: "new-booker-enabled" }], + }, + { + source: "/d/:link/:slug", + destination: "/new-booker/d/:link/:slug", + has: [{ type: "cookie", key: "new-booker-enabled" }], + }, + ], + // Keep cookie based booker enabled to test new-booker embed in production + ...[ + { + source: `/:user((?!${pages.join("|")}).*)/:type/embed`, + destination: "/new-booker/:user/:type/embed", + has: [{ type: "cookie", key: "new-booker-enabled" }], + }, + { + source: "/team/:slug/:type/embed", + destination: "/new-booker/team/:slug/:type/embed", + has: [{ type: "cookie", key: "new-booker-enabled" }], + }, + ], /* TODO: have these files being served from another deployment or CDN { source: "/embed/embed.js", destination: process.env.NEXT_PUBLIC_EMBED_LIB_URL?, }, */ - /** - * Header allows us to enable new-booker using middleware which uses env variables to enable/disable new booker - */ - { - source: `/:user((?!${pages.join("|")}).*)/:type`, - destination: "/new-booker/:user/:type", - has: [{ type: "header", key: "new-booker-enabled" }], - }, - { - source: `/:user((?!${pages.join("|")}).*)/:type/embed`, - destination: "/new-booker/:user/:type/embed", - has: [{ type: "header", key: "new-booker-enabled" }], - }, - { - source: "/team/:slug/:type", - destination: "/new-booker/team/:slug/:type", - has: [{ type: "header", key: "new-booker-enabled" }], - }, - { - source: "/team/:slug/:type/embed", - destination: "/new-booker/team/:slug/:type/embed", - has: [{ type: "header", key: "new-booker-enabled" }], - }, - { - source: "/d/:link/:slug", - destination: "/new-booker/d/:link/:slug", - has: [{ type: "header", key: "new-booker-enabled" }], - }, /** * Enables new booker using cookie. It works even if NEW_BOOKER_ENABLED_FOR_NON_EMBED, NEW_BOOKER_ENABLED_FOR_EMBED are disabled */ - { - source: `/:user((?!${pages.join("|")}).*)/:type`, - destination: "/new-booker/:user/:type", - has: [{ type: "cookie", key: "new-booker-enabled" }], - }, - { - source: `/:user((?!${pages.join("|")}).*)/:type/embed`, - destination: "/new-booker/:user/:type/embed", - has: [{ type: "cookie", key: "new-booker-enabled" }], - }, - { - source: "/team/:slug/:type", - destination: "/new-booker/team/:slug/:type", - has: [{ type: "cookie", key: "new-booker-enabled" }], - }, - { - source: "/team/:slug/:type/embed", - destination: "/new-booker/team/:slug/:type/embed", - has: [{ type: "cookie", key: "new-booker-enabled" }], - }, - { - source: "/d/:link/:slug", - destination: "/new-booker/d/:link/:slug", - has: [{ type: "cookie", key: "new-booker-enabled" }], - }, ]; + + // Enable New Booker for all Embed Requests + if (process.env.NEW_BOOKER_ENABLED_FOR_EMBED === "1") { + console.log("Enabling New Booker for Embed"); + rewrites.push( + ...[ + { + source: `/:user((?!${pages.join("|")}).*)/:type/embed`, + destination: "/new-booker/:user/:type/embed", + }, + { + source: "/team/:slug/:type/embed", + destination: "/new-booker/team/:slug/:type/embed", + }, + ] + ); + } + + // Enable New Booker for All but embed Requests + if (process.env.NEW_BOOKER_ENABLED_FOR_NON_EMBED === "1") { + console.log("Enabling New Booker for Non-Embed"); + rewrites.push( + ...[ + { + source: userTypeRouteRegExp, + destination: "/new-booker/:user/:type", + }, + { + source: "/team/:slug/:type", + destination: "/new-booker/team/:slug/:type", + }, + { + source: "/d/:link/:slug", + destination: "/new-booker/d/:link/:slug", + }, + ] + ); + } + return rewrites; }, async headers() { return [ diff --git a/packages/embeds/embed-core/embed-iframe-init.ts b/packages/embeds/embed-core/embed-iframe-init.ts index a48999b147..e669599c61 100644 --- a/packages/embeds/embed-core/embed-iframe-init.ts +++ b/packages/embeds/embed-core/embed-iframe-init.ts @@ -5,7 +5,10 @@ export default function EmbedInitIframe() { return; } - const embedNameSpaceFromQueryParam = new URL(document.URL).searchParams.get("embed"); + const url = new URL(document.URL); + const embedNameSpaceFromQueryParam = url.searchParams.get("embed"); + const hasEmbedPath = url.pathname.endsWith("/embed"); + const defaultNamespace = ""; // Namespace is initially set in query param `embed` but the query param might get lost during soft navigation // So, we also check for the namespace in `window.name` which is set when iframe is created by embed.ts and persists for the duration of iframe's life @@ -17,6 +20,8 @@ export default function EmbedInitIframe() { ? embedNameSpaceFromQueryParam : window.name.includes("cal-embed=") ? window.name.replace(/cal-embed=(.*)/, "$1").trim() + : hasEmbedPath + ? defaultNamespace // If query param is not there but /embed is used then assume default namespace. : null; window.isEmbed = () => { diff --git a/packages/embeds/embed-core/src/embed-iframe.ts b/packages/embeds/embed-core/src/embed-iframe.ts index 3cd31d4572..6f6b609fa6 100644 --- a/packages/embeds/embed-core/src/embed-iframe.ts +++ b/packages/embeds/embed-core/src/embed-iframe.ts @@ -2,7 +2,6 @@ import { useRouter } from "next/router"; import type { CSSProperties } from "react"; import { useState, useEffect } from "react"; -import embedInit from "@calcom/embed-core/embed-iframe-init"; import type { BookerStore } from "@calcom/features/bookings/Booker/store"; import type { BookerLayouts } from "@calcom/prisma/zod-utils"; @@ -439,8 +438,6 @@ function keepParentInformedAboutDimensionChanges() { if (isBrowser) { log("Embed SDK loaded", { isEmbed: window?.isEmbed?.() || false }); - // Exposes certain global variables/fns that are used by the app to get interface with the embed. - embedInit(); const url = new URL(document.URL); embedStore.theme = window?.getEmbedTheme?.(); if (url.searchParams.get("prerender") !== "true" && window?.isEmbed?.()) { diff --git a/packages/embeds/embed-core/src/sdk-event.ts b/packages/embeds/embed-core/src/sdk-event.ts index 3d33d9f819..aca7180626 100644 --- a/packages/embeds/embed-core/src/sdk-event.ts +++ b/packages/embeds/embed-core/src/sdk-event.ts @@ -2,9 +2,12 @@ * @file * This module is supposed to instantiate the SDK with appropriate namespace */ +import embedInit from "@calcom/embed-core/embed-iframe-init"; + import { SdkActionManager } from "./sdk-action-manager"; export let sdkActionManager: SdkActionManager | null = null; if (typeof window !== "undefined") { - sdkActionManager = new SdkActionManager(new URL(document.URL).searchParams.get("embed")); + embedInit(); + sdkActionManager = new SdkActionManager(window.getEmbedNamespace()); }