fix: ensure linkFailed event fires for 404, 500, and 403 errors in embeds (#25261)

- Add CalComPageStatus handling in NotFound and ErrorPage components using useLayoutEffect
- Remove redundant pageStatus logic from PageWrapperAppDir.tsx since App Router error/notFound pages set status themselves
- Refactor embed-iframe.ts: split checkPageStatusAndHandleError into hasPageError() and handlePageError()
- Add page status checks before firing linkReady to catch errors set after initialization
- Ensures linkFailed event fires correctly for all error status codes in embed scenarios

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Hariom Balhara
2025-12-18 16:30:32 +00:00
committed by GitHub
co-authored by Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 945ded951f
commit 71792bdeaa
4 changed files with 61 additions and 36 deletions
+11 -6
View File
@@ -2,6 +2,7 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useLayoutEffect } from "react";
import {
getOrgDomainConfigFromHostname,
@@ -40,9 +41,8 @@ function getPageInfo(pathname: string, host: string) {
return {
username: currentOrgDomain ?? "",
pageType: PageType.ORG,
url: `${WEBSITE_URL}/signup?callbackUrl=settings/organizations/new%3Fslug%3D${
currentOrgDomain?.replace("/", "") ?? ""
}`,
url: `${WEBSITE_URL}/signup?callbackUrl=settings/organizations/new%3Fslug%3D${currentOrgDomain?.replace("/", "") ?? ""
}`,
};
}
}
@@ -55,6 +55,12 @@ export function NotFound({ host }: { host: string }) {
const isSubpage = pathname?.includes("/", 2) || isBookingSuccessPage;
const isInsights = pathname?.startsWith("/insights");
useLayoutEffect(() => {
if (typeof window !== "undefined") {
window.CalComPageStatus = "404";
}
}, []);
const links = [
{
title: t("enterprise"),
@@ -156,9 +162,8 @@ export function NotFound({ host }: { host: string }) {
<span className="focus:outline-none">
<span className="absolute inset-0" aria-hidden="true" />
{t("register")}{" "}
<strong className="text-green-500">{`${
pageType === PageType.TEAM ? `${new URL(WEBSITE_URL).host}/team/` : ""
}${username}${pageType === PageType.ORG ? `.${subdomainSuffix()}` : ""}`}</strong>
<strong className="text-green-500">{`${pageType === PageType.TEAM ? `${new URL(WEBSITE_URL).host}/team/` : ""
}${username}${pageType === PageType.ORG ? `.${subdomainSuffix()}` : ""}`}</strong>
</span>
</span>
</h3>
-20
View File
@@ -1,8 +1,5 @@
"use client";
import { usePathname } from "next/navigation";
import Script from "next/script";
import "@calcom/embed-core/src/embed-iframe";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
@@ -16,16 +13,6 @@ export type PageWrapperProps = Readonly<{
}>;
function PageWrapper(props: PageWrapperProps) {
const pathname = usePathname();
let pageStatus = "200";
if (pathname === "/404") {
pageStatus = "404";
} else if (pathname === "/500") {
pageStatus = "500";
} else if (pathname === "/403") {
pageStatus = "403";
}
// On client side don't let nonce creep into DOM
// It also avoids hydration warning that says that Client has the nonce value but server has "" because browser removes nonce attributes before DOM is built
@@ -41,13 +28,6 @@ function PageWrapper(props: PageWrapperProps) {
<>
<AppProviders {...providerProps}>
<>
<Script
nonce={nonce}
id="page-status"
// It is strictly not necessary to disable, but in a future update of react/no-danger this will error.
// And we don't want it to error here anyways
dangerouslySetInnerHTML={{ __html: `window.CalComPageStatus = '${pageStatus}'` }}
/>
{props.requiresLicense ? (
<LicenseRequired>{props.children}</LicenseRequired>
) : (
+9 -1
View File
@@ -1,6 +1,6 @@
"use client";
import React from "react";
import { useLayoutEffect } from "react";
import "@calcom/embed-core/src/embed-iframe";
import { HttpError } from "@calcom/lib/http-error";
@@ -59,6 +59,14 @@ export const ErrorPage: React.FC<Props> = (props) => {
window.location.reload();
props.reset?.();
};
// useLayoutEffect runs synchronously before browser paint, ensuring it's set early
useLayoutEffect(() => {
if (statusCode && typeof window !== "undefined") {
window.CalComPageStatus = statusCode.toString();
}
}, [statusCode]);
return (
<>
<div className="bg-subtle flex h-screen">
+41 -9
View File
@@ -414,6 +414,12 @@ export const methods = {
return;
}
// Check page status again before firing linkReady, in case it was set after initialization
if (hasPageError()) {
handlePageError(window.CalComPageStatus);
return;
}
makeBodyVisible();
log("renderState is 'completed'");
embedStore.renderState = "completed";
@@ -587,6 +593,29 @@ function main() {
}
}
/**
* Checks if there's a page error (non-200 status).
* @returns true if an error exists, false otherwise
*/
function hasPageError() {
const pageStatus = window.CalComPageStatus;
return !!(pageStatus && pageStatus != "200");
}
/**
* Handles a page error by firing the linkFailed event.
* @param pageStatus - The error status code (e.g., "404", "500", "403")
*/
function handlePageError(pageStatus: string) {
sdkActionManager?.fire("linkFailed", {
code: pageStatus,
msg: "Problem loading the link",
data: {
url: document.URL,
},
});
}
function initializeAndSetupEmbed() {
sdkActionManager?.fire("__iframeReady", {
isPrerendering: isPrerendering(),
@@ -604,16 +633,12 @@ function initializeAndSetupEmbed() {
// HACK
const pageStatus = window.CalComPageStatus;
if (!pageStatus || pageStatus == "200") {
if (hasPageError()) {
handlePageError(pageStatus);
return;
} else {
keepParentInformedAboutDimensionChanges({ embedStore });
} else
sdkActionManager?.fire("linkFailed", {
code: pageStatus,
msg: "Problem loading the link",
data: {
url: document.URL,
},
});
}
}
function runAllUiSetters(uiConfig: UiConfig) {
@@ -665,6 +690,13 @@ async function connectPreloadedEmbed({
runAsap(tryToFireLinkReady);
return;
}
// Check page status again before firing linkReady, in case it was set after initialization
if (hasPageError()) {
handlePageError(window.CalComPageStatus);
resolve();
return;
}
// link is ready now, so we could stop doing it.
// Also the page is visible to user now.
stopEnsuringQueryParamsInUrl();