diff --git a/packages/embeds/embed-core/playwright/tests/action-based.e2e.ts b/packages/embeds/embed-core/playwright/tests/action-based.e2e.ts index 34e7fe4973..529100955d 100644 --- a/packages/embeds/embed-core/playwright/tests/action-based.e2e.ts +++ b/packages/embeds/embed-core/playwright/tests/action-based.e2e.ts @@ -321,6 +321,13 @@ test.describe("Popup Tests", () => { await expect(embedIframe).toBeEmbedCalLink(calNamespace, embeds.getActionFiredDetails, { pathname: "/free/30min", }); + + if (!embedIframe) { + throw new Error("Embed iframe not found"); + } + + // Verify that event type details are hidden (hideEventTypeDetails: true was set via ui() call) + await expect(embedIframe.locator('[data-testid="event-meta"]')).toBeHidden(); }); }); diff --git a/packages/embeds/embed-core/src/embed.ts b/packages/embeds/embed-core/src/embed.ts index 7dcbcddd18..71bd7a86a0 100644 --- a/packages/embeds/embed-core/src/embed.ts +++ b/packages/embeds/embed-core/src/embed.ts @@ -1,33 +1,35 @@ /// -import { FloatingButton } from "./FloatingButton/FloatingButton"; -import { Inline } from "./Inline/inline"; -import { ModalBox } from "./ModalBox/ModalBox"; + import { EMBED_MODAL_IFRAME_FORCE_RELOAD_THRESHOLD_MS, EMBED_MODAL_IFRAME_SLOT_STALE_TIME, EMBED_MODAL_PRERENDER_PREVENT_THRESHOLD_MS, } from "./constants"; -import type { InterfaceWithParent, interfaceWithParent } from "./embed-iframe"; import css from "./embed.css?inline"; +import type { InterfaceWithParent, interfaceWithParent } from "./embed-iframe"; +import { FloatingButton } from "./FloatingButton/FloatingButton"; +import { Inline } from "./Inline/inline"; import { getScrollableAncestor } from "./lib/domUtils"; import { getScrollByDistanceHandler } from "./lib/eventHandlers/scrollByDistanceEventHandler"; import { + buildConfigWithPrerenderRelatedFields, fromEntriesWithDuplicateKeys, - isRouterPath, generateDataAttributes, getConfigProp, + isRouterPath, isSameBookingLink, - buildConfigWithPrerenderRelatedFields, } from "./lib/utils"; -import { SdkActionManager } from "./sdk-action-manager"; +import { ModalBox } from "./ModalBox/ModalBox"; import type { EventData, EventDataMap } from "./sdk-action-manager"; +import { SdkActionManager } from "./sdk-action-manager"; import tailwindCss from "./tailwindCss"; -import type { UiConfig, EmbedPageType, PrefillAndIframeAttrsConfig, ModalPrerenderOptions } from "./types"; +import type { EmbedPageType, ModalPrerenderOptions, PrefillAndIframeAttrsConfig, UiConfig } from "./types"; import { getMaxHeightForModal } from "./ui-utils"; // Exporting for consumption by @calcom/embed-core user export type { EmbedEvent } from "./sdk-action-manager"; export type { PrefillAndIframeAttrsConfig } from "./types"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any type Rest = T extends [any, ...infer U] ? U : never; export type Message = { @@ -177,8 +179,8 @@ type SingleInstructionMap = { off: ["off", allPossibleCallbacksAndActions]; } & { [K in Exclude]: CalApi[K] extends (...args: never[]) => void - ? [K, ...Parameters] - : never; + ? [K, ...Parameters] + : never; }; type SingleInstruction = SingleInstructionMap[keyof SingleInstructionMap]; @@ -231,6 +233,11 @@ export class Cal { api: CalApi; + /** + * Commands for which the queue persists across iframe resets + */ + commandsPersistAcrossIframeResets: DoInIframeArg['method'][] = ["ui"]; + isPrerendering?: boolean; static actionsManagers: Record; @@ -361,14 +368,13 @@ export class Cal { } // Merge searchParams from config onto the URL which might have query params already - // @ts-ignore TS2802: URLSearchParams iteration requires downlevelIteration - for (const [key, value] of searchParams) { + searchParams.forEach((value, key) => { urlInstance.searchParams.append(key, value); - } + }); - // Very Important:Reset iframe ready flag and clear queue, as iframe might load a fresh URL and we need to check when it is ready. + // Very Important: Reset iframe ready flag and clear queue, as iframe might load a fresh URL and we need to check correctly when it is ready. this.iframeReset(); - + if (iframe.src === urlInstance.toString()) { // Ensure reload occurs even if the url is same - Though browser normally does it, but would be better to ensure it // This param has no other purpose except to ensure forced reload. @@ -408,7 +414,10 @@ export class Cal { iframeReset() { this.iframeReady = false; - this.iframeDoQueue = []; + // Only keep UI related instructions in the queue, as we want to ensure that newly loaded iframe has the same UI configuration applied automatically + this.iframeDoQueue = this.iframeDoQueue.filter((doInIframeArg) => + this.commandsPersistAcrossIframeResets.includes(doInIframeArg.method) + ); } constructor(namespace: string, q: Queue) { @@ -604,7 +613,7 @@ export class Cal { : 0; const crossedReloadThreshold = previousEmbedRenderStartTime ? timeSinceLastRender > - (prerenderOptions?.iframeForceReloadThresholdMs ?? EMBED_MODAL_IFRAME_FORCE_RELOAD_THRESHOLD_MS) + (prerenderOptions?.iframeForceReloadThresholdMs ?? EMBED_MODAL_IFRAME_FORCE_RELOAD_THRESHOLD_MS) : false; const areSlotsStale = previousEmbedRenderStartTime @@ -862,7 +871,7 @@ class CalApi { CalApi.initializedNamespaces.push(this.cal.namespace); - const { calOrigin: calOrigin, origin: origin, ...restConfig } = config; + const { calOrigin, origin, ...restConfig } = config; this.cal.__config.calOrigin = calOrigin || origin || this.cal.__config.calOrigin; @@ -950,10 +959,10 @@ class CalApi { template.innerHTML = ` `; @@ -1187,7 +1196,7 @@ class CalApi { config: enrichedConfig, }); // Send reloadInitiated message to iframe so it can track it - // Send it after loadInIframe so that new iframe can process it. + // Send it after loadInIframe so that new iframe can process it. this.cal.doInIframe({ method: "__reloadInitiated", arg: {} }); } else if (actionToTake === "connect" || actionToTake === "connect-no-slots-fetch") { const paramsToAdd = fromEntriesWithDuplicateKeys(calLinkUrlObject.searchParams.entries()); @@ -1196,8 +1205,8 @@ class CalApi { ...enrichedConfig, ...(actionToTake === "connect-no-slots-fetch" ? { - "cal.embed.noSlotsFetchOnConnect": "true", - } + "cal.embed.noSlotsFetchOnConnect": "true", + } : {}), }, params: paramsToAdd, @@ -1239,10 +1248,10 @@ class CalApi { template.innerHTML = ` `; this.cal.modalBox = template.content.children[0]; @@ -1303,9 +1312,9 @@ class CalApi { /** * Closes modal-based embeds programmatically. - * + * * @throws {Error} If called on an inline embed (only works for modal-based embeds) - * + * * @example * ```javascript * // Close the modal after a successful booking @@ -1487,6 +1496,7 @@ class CalApi { }, }); + this.cal.doInIframe({ method: "ui", arg: uiConfig }); } }