Compare commits

...

4 Commits

Author SHA1 Message Date
Anshuman Pandey
fac7369b3c fix: [Backport] backports welcome card image bug fix (#7545) 2026-03-20 11:52:51 +01:00
Anshuman Pandey
913ab98d62 fix: [Backport] backports the sdk initialization issues (#7536) 2026-03-19 14:33:20 +01:00
Anshuman Pandey
717a172ce0 fix: [Backport] backports sentry improvement and loading page fix (#7534) 2026-03-19 18:29:13 +05:30
pandeymangg
8c935f20c2 backports sentry improvement a loading page fix 2026-03-19 16:45:44 +05:30
5 changed files with 105 additions and 23 deletions

View File

@@ -1,7 +1,19 @@
import * as Sentry from "@sentry/nextjs";
import { type Instrumentation } from "next";
import { isExpectedError } from "@formbricks/types/errors";
import { IS_PRODUCTION, PROMETHEUS_ENABLED, SENTRY_DSN } from "@/lib/constants";
export const onRequestError = Sentry.captureRequestError;
export const onRequestError: Instrumentation.onRequestError = (...args) => {
const [error] = args;
// Skip expected business-logic errors (AuthorizationError, ResourceNotFoundError, etc.)
// These are handled gracefully in the UI and don't need server-side Sentry reporting
if (error instanceof Error && isExpectedError(error)) {
return;
}
Sentry.captureRequestError(...args);
};
export const register = async () => {
if (process.env.NEXT_RUNTIME === "nodejs") {

View File

@@ -11,7 +11,7 @@ export const TagsLoading = () => {
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.workspace_configuration")}>
<ProjectConfigNavigation activeId="tags" />
<ProjectConfigNavigation activeId="tags" loading />
</PageHeader>
<SettingsCard
title={t("environments.workspace.tags.manage_tags")}

View File

@@ -129,8 +129,10 @@ export const EditWelcomeCard = ({
allowedFileExtensions={["png", "jpeg", "jpg", "webp", "heic"]}
environmentId={environmentId}
onFileUpload={(url: string[] | undefined, _fileType: "image" | "video") => {
if (url?.[0]) {
if (url?.length) {
updateSurvey({ fileUrl: url[0] });
} else {
updateSurvey({ fileUrl: undefined });
}
}}
fileUrl={localSurvey?.welcomeCard?.fileUrl}

View File

@@ -6,7 +6,7 @@ import { Logger } from "@/lib/common/logger";
import { getIsSetup, setIsSetup } from "@/lib/common/status";
import { filterSurveys, getIsDebug, isNowExpired, wrapThrows } from "@/lib/common/utils";
import { fetchEnvironmentState } from "@/lib/environment/state";
import { closeSurvey } from "@/lib/survey/widget";
import { closeSurvey, preloadSurveysScript } from "@/lib/survey/widget";
import { DEFAULT_USER_STATE_NO_USER_ID } from "@/lib/user/state";
import { sendUpdatesToBackend } from "@/lib/user/update";
import {
@@ -316,6 +316,9 @@ export const setup = async (
addEventListeners();
addCleanupEventListeners();
// Preload surveys script so it's ready when a survey triggers
preloadSurveysScript(configInput.appUrl);
setIsSetup(true);
logger.debug("Set up complete");

View File

@@ -106,7 +106,15 @@ export const renderWidget = async (
const overlay = projectOverwrites.overlay ?? project.overlay;
const placement = projectOverwrites.placement ?? project.placement;
const isBrandingEnabled = project.inAppSurveyBranding;
const formbricksSurveys = await loadFormbricksSurveysExternally();
let formbricksSurveys: TFormbricksSurveys;
try {
formbricksSurveys = await loadFormbricksSurveysExternally();
} catch (error) {
logger.error(`Failed to load surveys library: ${String(error)}`);
setIsSurveyRunning(false);
return;
}
const recaptchaSiteKey = config.get().environment.data.recaptchaSiteKey;
const isSpamProtectionEnabled = Boolean(recaptchaSiteKey && survey.recaptcha?.enabled);
@@ -219,30 +227,87 @@ export const removeWidgetContainer = (): void => {
document.getElementById(CONTAINER_ID)?.remove();
};
const loadFormbricksSurveysExternally = (): Promise<typeof globalThis.window.formbricksSurveys> => {
const config = Config.getInstance();
const SURVEYS_LOAD_TIMEOUT_MS = 10000;
const SURVEYS_POLL_INTERVAL_MS = 200;
type TFormbricksSurveys = typeof globalThis.window.formbricksSurveys;
let surveysLoadPromise: Promise<TFormbricksSurveys> | null = null;
const waitForSurveysGlobal = (): Promise<TFormbricksSurveys> => {
return new Promise((resolve, reject) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- We need to check if the formbricksSurveys object exists
if (globalThis.window.formbricksSurveys) {
resolve(globalThis.window.formbricksSurveys);
} else {
const script = document.createElement("script");
script.src = `${config.get().appUrl}/js/surveys.umd.cjs`;
script.async = true;
script.onload = () => {
// Apply stored nonce if it was set before surveys package loaded
const startTime = Date.now();
const check = (): void => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Runtime check for surveys package availability
if (globalThis.window.formbricksSurveys) {
const storedNonce = globalThis.window.__formbricksNonce;
if (storedNonce) {
globalThis.window.formbricksSurveys.setNonce(storedNonce);
}
resolve(globalThis.window.formbricksSurveys);
};
script.onerror = (error) => {
console.error("Failed to load Formbricks Surveys library:", error);
reject(new Error(`Failed to load Formbricks Surveys library: ${error as string}`));
};
document.head.appendChild(script);
}
return;
}
if (Date.now() - startTime >= SURVEYS_LOAD_TIMEOUT_MS) {
reject(new Error("Formbricks Surveys library did not become available within timeout"));
return;
}
setTimeout(check, SURVEYS_POLL_INTERVAL_MS);
};
check();
});
};
const loadFormbricksSurveysExternally = (): Promise<TFormbricksSurveys> => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Runtime check for surveys package availability
if (globalThis.window.formbricksSurveys) {
return Promise.resolve(globalThis.window.formbricksSurveys);
}
if (surveysLoadPromise) {
return surveysLoadPromise;
}
surveysLoadPromise = new Promise<TFormbricksSurveys>((resolve, reject: (error: unknown) => void) => {
const config = Config.getInstance();
const script = document.createElement("script");
script.src = `${config.get().appUrl}/js/surveys.umd.cjs`;
script.async = true;
script.onload = () => {
waitForSurveysGlobal()
.then(resolve)
.catch((error: unknown) => {
surveysLoadPromise = null;
console.error("Failed to load Formbricks Surveys library:", error);
reject(new Error(`Failed to load Formbricks Surveys library`));
});
};
script.onerror = (error) => {
surveysLoadPromise = null;
console.error("Failed to load Formbricks Surveys library:", error);
reject(new Error(`Failed to load Formbricks Surveys library`));
};
document.head.appendChild(script);
});
return surveysLoadPromise;
};
let isPreloaded = false;
export const preloadSurveysScript = (appUrl: string): void => {
// Don't preload if already loaded or already preloading
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Runtime check for surveys package availability
if (globalThis.window.formbricksSurveys) return;
if (isPreloaded) return;
isPreloaded = true;
const link = document.createElement("link");
link.rel = "preload";
link.as = "script";
link.href = `${appUrl}/js/surveys.umd.cjs`;
document.head.appendChild(link);
};