Compare commits

..

1 Commits

Author SHA1 Message Date
Matti Nannt 2a103cc9f3 fix: move SAML jackson opts out of module scope into init()
`opts` was declared as a module-scope const in a `"use server"` file,
which react-doctor flagged as `server-no-mutable-module-state`
(Server, error). Although the object happens to be read-only today,
the container itself is shared across requests — any future mutation
(e.g. dynamically overriding `db.url`) would silently affect every
request.

Move the object construction inside `init()`, gated by the same
"controllers not yet initialized" check that already wraps the
expensive setup. This is a no-op for cache-hit calls (object is never
constructed) and behaviorally identical for the first call.

The intentional `globalThis` singleton cache for the SAML controllers
themselves is left in place — that's a deliberate cross-request cache,
not unintentional shared state.

Verified via `pnpm --filter @formbricks/web test`: 5166/5166 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 14:56:40 +02:00
3 changed files with 308 additions and 312 deletions
+11 -11
View File
@@ -5,17 +5,6 @@ import { SAML_AUDIENCE, SAML_DATABASE_URL, SAML_PATH, WEBAPP_URL } from "@/lib/c
import { preloadConnection } from "@/modules/ee/auth/saml/lib/preload-connection";
import { getIsSamlSsoEnabled } from "@/modules/ee/license-check/lib/utils";
const opts: JacksonOption = {
externalUrl: WEBAPP_URL,
samlAudience: SAML_AUDIENCE,
samlPath: SAML_PATH,
db: {
engine: "sql",
type: "postgres",
url: SAML_DATABASE_URL,
},
};
declare global {
var oauthController: IOAuthController | undefined;
var connectionController: IConnectionAPIController | undefined;
@@ -28,6 +17,17 @@ export default async function init() {
const isSamlSsoEnabled = await getIsSamlSsoEnabled();
if (!isSamlSsoEnabled) return;
const opts: JacksonOption = {
externalUrl: WEBAPP_URL,
samlAudience: SAML_AUDIENCE,
samlPath: SAML_PATH,
db: {
engine: "sql",
type: "postgres",
url: SAML_DATABASE_URL,
},
};
const ret = await (await import("@boxyhq/saml-jackson")).controllers(opts);
await preloadConnection(ret.connectionAPIController);
@@ -1,7 +1,7 @@
"use client";
import { Workspace } from "@prisma/client";
import { MotionConfig, motion } from "framer-motion";
import { motion } from "framer-motion";
import { ExpandIcon, GlobeIcon, MonitorIcon, ShrinkIcon, SmartphoneIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -202,180 +202,70 @@ export const PreviewSurvey = ({
};
return (
<MotionConfig reducedMotion="user">
<div
className="flex h-full w-full flex-col items-center justify-items-center p-2 py-4"
id="survey-preview">
<motion.div
className={cn(
"z-50 flex h-full w-fit items-center justify-center",
isFullScreenPreview && "h-full w-full bg-zinc-500/50 backdrop-blur-md"
)}
style={{
position: isFullScreenPreview ? "fixed" : "absolute",
zIndex: 50,
left: isFullScreenPreview ? 0 : undefined,
top: isFullScreenPreview ? 0 : undefined,
}}
transition={{
ease: "easeInOut",
delay: 1.5,
}}
/>
<motion.div
layout
style={{
left: isFullScreenPreview ? "2.5%" : undefined,
top: isFullScreenPreview ? 0 : undefined,
}}
transition={{
duration: 0.8,
ease: "easeInOut",
type: "spring",
}}
className={cn(
"z-50 flex h-[95%] w-full items-center justify-center overflow-hidden rounded-lg border border-slate-300",
isFullScreenPreview && "absolute z-50 h-[95%] w-[95%]"
)}>
{previewMode === "mobile" && (
<>
<p className="absolute left-0 top-0 m-2 rounded bg-slate-100 px-2 py-1 text-xs text-slate-400">
{t("common.preview")}
</p>
<div className="absolute right-0 top-0 m-2 flex items-center gap-1">
{showLanguageSelector && (
<LanguageSelector
languages={enabledLanguages}
languageCode={languageCode}
setLanguageCode={setLanguageCode}
locale={locale}
/>
)}
<ResetProgressButton onClick={resetProgress} />
</div>
<MediaBackground
surveyType={survey.type}
styling={styling}
ContentRef={ContentRef as React.RefObject<HTMLDivElement>}
isMobilePreview>
{previewType === "modal" ? (
<Modal
isOpen={isModalOpen}
placement={placement}
previewMode="mobile"
overlay={overlay}
clickOutsideClose={clickOutsideClose}
borderRadius={styling?.roundness ?? 8}
background={styling?.cardBackgroundColor?.light}>
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
survey={toJsWorkspaceStateSurvey(survey)}
isBrandingEnabled={workspace.inAppSurveyBranding}
isRedirectDisabled={true}
languageCode={languageCode}
styling={styling}
isCardBorderVisible={!styling.highlightBorderColor?.light}
onClose={handlePreviewModalClose}
getSetBlockId={(f: (value: string) => void) => {
setBlockId = f;
}}
onFinished={onFinished}
placement={placement}
isSpamProtectionEnabled={isSpamProtectionEnabled}
/>
</Modal>
) : (
<div className="flex h-full w-full flex-col justify-center px-1">
<div className="absolute left-5 top-5">
{!styling.isLogoHidden && (
<ClientLogo workspaceLogo={workspace.logo} surveyLogo={styling.logo} previewSurvey />
)}
</div>
<div className="z-10 w-full rounded-lg border border-transparent">
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
isBrandingEnabled={workspace.linkSurveyBranding}
survey={toJsWorkspaceStateSurvey({ ...survey, type: "link" })}
languageCode={languageCode}
responseCount={42}
styling={styling}
getSetBlockId={(f: (value: string) => void) => {
setBlockId = f;
}}
isSpamProtectionEnabled={isSpamProtectionEnabled}
/>
</div>
</div>
)}
</MediaBackground>
</>
)}
{previewMode === "desktop" && (
<div className="flex h-full w-full flex-1 flex-col">
<div className="flex h-8 w-full items-center rounded-t-lg bg-slate-100">
<div className="ml-6 flex space-x-2">
<div className="h-3 w-3 rounded-full bg-red-500"></div>
<div className="h-3 w-3 rounded-full bg-amber-500"></div>
<button
className="h-3 w-3 cursor-pointer rounded-full bg-emerald-500"
onClick={() => {
if (isFullScreenPreview) {
setIsFullScreenPreview(false);
} else {
setIsFullScreenPreview(true);
}
}}
aria-label={
isFullScreenPreview
? t("workspace.surveys.edit.shrink_preview")
: t("workspace.surveys.edit.expand_preview")
}></button>
</div>
<div className="ml-4 flex w-full justify-between font-mono text-sm text-slate-400">
<p>
{previewType === "modal" ? t("workspace.surveys.edit.your_web_app") : t("common.preview")}
</p>
<div className="flex items-center">
{showLanguageSelector && (
<LanguageSelector
languages={enabledLanguages}
languageCode={languageCode}
setLanguageCode={setLanguageCode}
locale={locale}
/>
)}
{isFullScreenPreview ? (
<ShrinkIcon
className="mr-1 h-[22px] w-[22px] cursor-pointer rounded-md bg-white p-1 text-slate-500 hover:text-slate-700"
onClick={() => {
setIsFullScreenPreview(false);
}}
/>
) : (
<ExpandIcon
className="mr-1 h-[22px] w-[22px] cursor-pointer rounded-md bg-white p-1 text-slate-500 hover:text-slate-700"
onClick={() => {
setIsFullScreenPreview(true);
}}
/>
)}
<ResetProgressButton onClick={resetProgress} />
</div>
</div>
</div>
<div
className="flex h-full w-full flex-col items-center justify-items-center p-2 py-4"
id="survey-preview">
<motion.div
className={cn(
"z-50 flex h-full w-fit items-center justify-center",
isFullScreenPreview && "h-full w-full bg-zinc-500/50 backdrop-blur-md"
)}
style={{
position: isFullScreenPreview ? "fixed" : "absolute",
zIndex: 50,
left: isFullScreenPreview ? 0 : undefined,
top: isFullScreenPreview ? 0 : undefined,
}}
transition={{
ease: "easeInOut",
delay: 1.5,
}}
/>
<motion.div
layout
style={{
left: isFullScreenPreview ? "2.5%" : undefined,
top: isFullScreenPreview ? 0 : undefined,
}}
transition={{
duration: 0.8,
ease: "easeInOut",
type: "spring",
}}
className={cn(
"z-50 flex h-[95%] w-full items-center justify-center overflow-hidden rounded-lg border border-slate-300",
isFullScreenPreview && "absolute z-50 h-[95%] w-[95%]"
)}>
{previewMode === "mobile" && (
<>
<p className="absolute left-0 top-0 m-2 rounded bg-slate-100 px-2 py-1 text-xs text-slate-400">
{t("common.preview")}
</p>
<div className="absolute right-0 top-0 m-2 flex items-center gap-1">
{showLanguageSelector && (
<LanguageSelector
languages={enabledLanguages}
languageCode={languageCode}
setLanguageCode={setLanguageCode}
locale={locale}
/>
)}
<ResetProgressButton onClick={resetProgress} />
</div>
<MediaBackground
surveyType={survey.type}
styling={styling}
ContentRef={ContentRef as React.RefObject<HTMLDivElement>}
isMobilePreview>
{previewType === "modal" ? (
<Modal
isOpen={isModalOpen}
placement={placement}
clickOutsideClose={clickOutsideClose}
previewMode="mobile"
overlay={overlay}
previewMode="desktop"
borderRadius={styling.roundness ?? 8}
background={styling.cardBackgroundColor?.light}>
clickOutsideClose={clickOutsideClose}
borderRadius={styling?.roundness ?? 8}
background={styling?.cardBackgroundColor?.light}>
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
@@ -390,28 +280,23 @@ export const PreviewSurvey = ({
setBlockId = f;
}}
onFinished={onFinished}
isSpamProtectionEnabled={isSpamProtectionEnabled}
placement={placement}
isSpamProtectionEnabled={isSpamProtectionEnabled}
/>
</Modal>
) : (
<MediaBackground
surveyType={survey.type}
styling={styling}
ContentRef={ContentRef as React.RefObject<HTMLDivElement>}
isEditorView>
<div className="flex h-full w-full flex-col justify-center px-1">
<div className="absolute left-5 top-5">
{!styling.isLogoHidden && (
<ClientLogo workspaceLogo={workspace.logo} surveyLogo={styling.logo} previewSurvey />
)}
</div>
<div className="z-0 w-full max-w-4xl rounded-lg border-transparent">
<div className="z-10 w-full rounded-lg border border-transparent">
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
survey={toJsWorkspaceStateSurvey({ ...survey, type: "link" })}
isBrandingEnabled={workspace.linkSurveyBranding}
isRedirectDisabled={true}
survey={toJsWorkspaceStateSurvey({ ...survey, type: "link" })}
languageCode={languageCode}
responseCount={42}
styling={styling}
@@ -421,27 +306,140 @@ export const PreviewSurvey = ({
isSpamProtectionEnabled={isSpamProtectionEnabled}
/>
</div>
</MediaBackground>
</div>
)}
</div>
)}
</motion.div>
</MediaBackground>
</>
)}
{previewMode === "desktop" && (
<div className="flex h-full w-full flex-1 flex-col">
<div className="flex h-8 w-full items-center rounded-t-lg bg-slate-100">
<div className="ml-6 flex space-x-2">
<div className="h-3 w-3 rounded-full bg-red-500"></div>
<div className="h-3 w-3 rounded-full bg-amber-500"></div>
<button
className="h-3 w-3 cursor-pointer rounded-full bg-emerald-500"
onClick={() => {
if (isFullScreenPreview) {
setIsFullScreenPreview(false);
} else {
setIsFullScreenPreview(true);
}
}}
aria-label={
isFullScreenPreview
? t("workspace.surveys.edit.shrink_preview")
: t("workspace.surveys.edit.expand_preview")
}></button>
</div>
<div className="ml-4 flex w-full justify-between font-mono text-sm text-slate-400">
<p>
{previewType === "modal" ? t("workspace.surveys.edit.your_web_app") : t("common.preview")}
</p>
{/* for toggling between mobile and desktop mode */}
<div className="mt-2 flex rounded-full border-2 border-slate-300 p-1">
<TabOption
active={previewMode === "mobile"}
icon={<SmartphoneIcon className="mx-4 my-2 h-4 w-4 text-slate-700" />}
onClick={() => handlePreviewModeChange("mobile")}
/>
<TabOption
active={previewMode === "desktop"}
icon={<MonitorIcon className="mx-4 my-2 h-4 w-4 text-slate-700" />}
onClick={() => handlePreviewModeChange("desktop")}
/>
</div>
<div className="flex items-center">
{showLanguageSelector && (
<LanguageSelector
languages={enabledLanguages}
languageCode={languageCode}
setLanguageCode={setLanguageCode}
locale={locale}
/>
)}
{isFullScreenPreview ? (
<ShrinkIcon
className="mr-1 h-[22px] w-[22px] cursor-pointer rounded-md bg-white p-1 text-slate-500 hover:text-slate-700"
onClick={() => {
setIsFullScreenPreview(false);
}}
/>
) : (
<ExpandIcon
className="mr-1 h-[22px] w-[22px] cursor-pointer rounded-md bg-white p-1 text-slate-500 hover:text-slate-700"
onClick={() => {
setIsFullScreenPreview(true);
}}
/>
)}
<ResetProgressButton onClick={resetProgress} />
</div>
</div>
</div>
{previewType === "modal" ? (
<Modal
isOpen={isModalOpen}
placement={placement}
clickOutsideClose={clickOutsideClose}
overlay={overlay}
previewMode="desktop"
borderRadius={styling.roundness ?? 8}
background={styling.cardBackgroundColor?.light}>
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
survey={toJsWorkspaceStateSurvey(survey)}
isBrandingEnabled={workspace.inAppSurveyBranding}
isRedirectDisabled={true}
languageCode={languageCode}
styling={styling}
isCardBorderVisible={!styling.highlightBorderColor?.light}
onClose={handlePreviewModalClose}
getSetBlockId={(f: (value: string) => void) => {
setBlockId = f;
}}
onFinished={onFinished}
isSpamProtectionEnabled={isSpamProtectionEnabled}
placement={placement}
/>
</Modal>
) : (
<MediaBackground
surveyType={survey.type}
styling={styling}
ContentRef={ContentRef as React.RefObject<HTMLDivElement>}
isEditorView>
<div className="absolute left-5 top-5">
{!styling.isLogoHidden && (
<ClientLogo workspaceLogo={workspace.logo} surveyLogo={styling.logo} previewSurvey />
)}
</div>
<div className="z-0 w-full max-w-4xl rounded-lg border-transparent">
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
survey={toJsWorkspaceStateSurvey({ ...survey, type: "link" })}
isBrandingEnabled={workspace.linkSurveyBranding}
isRedirectDisabled={true}
languageCode={languageCode}
responseCount={42}
styling={styling}
getSetBlockId={(f: (value: string) => void) => {
setBlockId = f;
}}
isSpamProtectionEnabled={isSpamProtectionEnabled}
/>
</div>
</MediaBackground>
)}
</div>
)}
</motion.div>
{/* for toggling between mobile and desktop mode */}
<div className="mt-2 flex rounded-full border-2 border-slate-300 p-1">
<TabOption
active={previewMode === "mobile"}
icon={<SmartphoneIcon className="mx-4 my-2 h-4 w-4 text-slate-700" />}
onClick={() => handlePreviewModeChange("mobile")}
/>
<TabOption
active={previewMode === "desktop"}
icon={<MonitorIcon className="mx-4 my-2 h-4 w-4 text-slate-700" />}
onClick={() => handlePreviewModeChange("desktop")}
/>
</div>
</MotionConfig>
</div>
);
};
@@ -1,6 +1,6 @@
"use client";
import { MotionConfig, Variants, motion } from "framer-motion";
import { Variants, motion } from "framer-motion";
import { Fragment, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { TSurvey, TSurveyType } from "@formbricks/types/surveys/types";
@@ -131,113 +131,111 @@ export const ThemeStylingPreviewSurvey = ({
};
return (
<MotionConfig reducedMotion="user">
<div className="flex h-full w-full flex-col items-center justify-items-center overflow-hidden">
<motion.div
variants={previewParentContainerVariant}
className="fixed hidden h-[95%] w-5/6"
animate={isFullScreenPreview ? "expanded" : "shrink"}
/>
<motion.div
layout
variants={previewScreenVariants}
animate={
isFullScreenPreview
? previewPosition === "relative"
? "expanded"
: "expanded_with_fixed_positioning"
: "shrink"
}
className={cn(
"relative z-10 flex w-5/6 flex-col rounded-lg border border-slate-300 shadow-xl",
isAppSurvey ? "bg-slate-200" : "overflow-y-auto bg-white"
)}>
<div className="flex h-auto w-full items-center rounded-t-lg bg-slate-100 py-2">
<div className="ml-6 flex space-x-2">
<div className="h-3 w-3 rounded-full bg-red-500"></div>
<div className="h-3 w-3 rounded-full bg-amber-500"></div>
<div className="h-3 w-3 rounded-full bg-emerald-500"></div>
</div>
<div className="ml-4 flex w-full justify-between font-mono text-sm text-slate-400">
<p>{isAppSurvey ? t("workspace.surveys.edit.your_web_app") : t("common.preview")}</p>
<div className="flex h-full w-full flex-col items-center justify-items-center overflow-hidden">
<motion.div
variants={previewParentContainerVariant}
className="fixed hidden h-[95%] w-5/6"
animate={isFullScreenPreview ? "expanded" : "shrink"}
/>
<motion.div
layout
variants={previewScreenVariants}
animate={
isFullScreenPreview
? previewPosition === "relative"
? "expanded"
: "expanded_with_fixed_positioning"
: "shrink"
}
className={cn(
"relative z-10 flex w-5/6 flex-col rounded-lg border border-slate-300 shadow-xl",
isAppSurvey ? "bg-slate-200" : "overflow-y-auto bg-white"
)}>
<div className="flex h-auto w-full items-center rounded-t-lg bg-slate-100 py-2">
<div className="ml-6 flex space-x-2">
<div className="h-3 w-3 rounded-full bg-red-500"></div>
<div className="h-3 w-3 rounded-full bg-amber-500"></div>
<div className="h-3 w-3 rounded-full bg-emerald-500"></div>
</div>
<div className="ml-4 flex w-full justify-between font-mono text-sm text-slate-400">
<p>{isAppSurvey ? t("workspace.surveys.edit.your_web_app") : t("common.preview")}</p>
<div className="flex items-center">
<ResetProgressButton onClick={resetQuestionProgress} />
</div>
<div className="flex items-center">
<ResetProgressButton onClick={resetQuestionProgress} />
</div>
</div>
<div className="flex w-full flex-1 flex-col rounded-b-lg">
{isAppSurvey ? (
<Modal
isOpen
placement={placement}
clickOutsideClose={clickOutsideClose}
overlay={overlay}
previewMode="desktop"
background={workspace.styling.cardBackgroundColor?.light}
borderRadius={workspace.styling.roundness ?? 8}>
<Fragment key={surveyKey}>
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
survey={toJsWorkspaceStateSurvey({ ...survey, type: "app" })}
isBrandingEnabled={workspace.inAppSurveyBranding}
isRedirectDisabled={true}
onFileUpload={async (file) => file.name}
styling={styling}
isCardBorderVisible={!highlightBorderColor}
languageCode="default"
/>
</Fragment>
</Modal>
) : (
<MediaBackground
surveyType={survey.type}
styling={styling}
ContentRef={ContentRef as React.MutableRefObject<HTMLDivElement> | null}
isEditorView>
{!workspace.styling?.isLogoHidden && (
<button className="absolute left-5 top-5" onClick={scrollToEditLogoSection}>
<ClientLogo workspaceLogo={workspace.logo} previewSurvey />
</button>
)}
<div
key={surveyKey}
className={`${!workspace.styling.isLogoHidden && !isFullScreenPreview ? "mt-12" : ""} z-0 w-full max-w-md overflow-hidden rounded-lg p-4`}>
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
survey={toJsWorkspaceStateSurvey({ ...survey, type: "link" })}
isBrandingEnabled={workspace.linkSurveyBranding}
isRedirectDisabled={true}
onFileUpload={async (file) => file.name}
responseCount={42}
styling={styling}
languageCode="default"
/>
</div>
</MediaBackground>
)}
</div>
</motion.div>
{/* for toggling between mobile and desktop mode */}
<div className="mt-2 flex rounded-full border-2 border-slate-300 p-1">
<button
type="button"
className={`${previewType === "link" ? "rounded-full bg-slate-200" : ""} cursor-pointer px-3 py-1 text-sm`}
onClick={() => setPreviewType("link")}>
{t("common.link_survey")}
</button>
<button
type="button"
className={`${isAppSurvey ? "rounded-full bg-slate-200" : ""} cursor-pointer px-3 py-1 text-sm`}
onClick={() => setPreviewType("app")}>
{t("common.app_survey")}
</button>
</div>
<div className="flex w-full flex-1 flex-col rounded-b-lg">
{isAppSurvey ? (
<Modal
isOpen
placement={placement}
clickOutsideClose={clickOutsideClose}
overlay={overlay}
previewMode="desktop"
background={workspace.styling.cardBackgroundColor?.light}
borderRadius={workspace.styling.roundness ?? 8}>
<Fragment key={surveyKey}>
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
survey={toJsWorkspaceStateSurvey({ ...survey, type: "app" })}
isBrandingEnabled={workspace.inAppSurveyBranding}
isRedirectDisabled={true}
onFileUpload={async (file) => file.name}
styling={styling}
isCardBorderVisible={!highlightBorderColor}
languageCode="default"
/>
</Fragment>
</Modal>
) : (
<MediaBackground
surveyType={survey.type}
styling={styling}
ContentRef={ContentRef as React.MutableRefObject<HTMLDivElement> | null}
isEditorView>
{!workspace.styling?.isLogoHidden && (
<button className="absolute left-5 top-5" onClick={scrollToEditLogoSection}>
<ClientLogo workspaceLogo={workspace.logo} previewSurvey />
</button>
)}
<div
key={surveyKey}
className={`${!workspace.styling.isLogoHidden && !isFullScreenPreview ? "mt-12" : ""} z-0 w-full max-w-md overflow-hidden rounded-lg p-4`}>
<SurveyInline
appUrl={publicDomain}
isPreviewMode={true}
survey={toJsWorkspaceStateSurvey({ ...survey, type: "link" })}
isBrandingEnabled={workspace.linkSurveyBranding}
isRedirectDisabled={true}
onFileUpload={async (file) => file.name}
responseCount={42}
styling={styling}
languageCode="default"
/>
</div>
</MediaBackground>
)}
</div>
</motion.div>
{/* for toggling between mobile and desktop mode */}
<div className="mt-2 flex rounded-full border-2 border-slate-300 p-1">
<button
type="button"
className={`${previewType === "link" ? "rounded-full bg-slate-200" : ""} cursor-pointer px-3 py-1 text-sm`}
onClick={() => setPreviewType("link")}>
{t("common.link_survey")}
</button>
<button
type="button"
className={`${isAppSurvey ? "rounded-full bg-slate-200" : ""} cursor-pointer px-3 py-1 text-sm`}
onClick={() => setPreviewType("app")}>
{t("common.app_survey")}
</button>
</div>
</MotionConfig>
</div>
);
};