Compare commits

..

5 Commits

Author SHA1 Message Date
Cursor Agent
893384a19d fix: load survey script directly via src instead of blob URL
Fixes FORMBRICKS-TQ

The blob URL approach was being blocked by CSP which doesn't include 'blob:'
in script-src directive. Simplified to load script directly via src attribute:
- Works with existing CSP ('self' is allowed)
- No need for fetch, blob URLs, or eval
- Properly executes and initializes window.formbricksSurveys
- Adds cache-busting in development for fresh script loads
2026-03-18 13:05:54 +00:00
Cursor Agent
1eba663294 chore: remove test files 2026-03-18 12:33:26 +00:00
Cursor Agent
c37e5c0750 chore: add blob URL test results screenshot - all tests passing 2026-03-18 12:32:43 +00:00
Cursor Agent
75e47b4979 fix: use blob URL to execute survey script and initialize window.formbricksSurveys
Fixes FORMBRICKS-TQ

The script content was being assigned to textContent which may not execute
properly in all contexts. Changed to use a Blob URL approach which:
- Creates a blob from the fetched script content
- Loads it via script src attribute (works with CSP without unsafe-eval)
- Properly waits for script execution before proceeding
- Ensures window.formbricksSurveys is initialized correctly
- Cleans up the blob URL after loading to prevent memory leaks
2026-03-18 12:31:17 +00:00
Cursor Agent
7504c47fc1 fix: execute survey script in global scope to initialize window.formbricksSurveys
Fixes FORMBRICKS-TQ

The script content was being assigned to textContent instead of being
executed, preventing the window.formbricksSurveys object from being
initialized. Changed to use indirect eval pattern to execute the script
content in the global scope, ensuring proper initialization.
2026-03-18 12:22:53 +00:00
8 changed files with 26 additions and 205 deletions

View File

@@ -1,7 +1,6 @@
"use client";
import {
type CollisionDetection,
DndContext,
type DragEndEvent,
KeyboardSensor,
@@ -46,42 +45,6 @@ const SkeletonCell = () => (
</Skeleton>
);
/**
* Safari-safe collision detection that validates getBoundingClientRect() properties
* before using them. Safari can sometimes return DOMRect objects with undefined
* properties (particularly 'top'), which causes dnd-kit to throw TypeErrors.
*/
const safariSafeClosestCenter: CollisionDetection = (args) => {
const { droppableContainers } = args;
// Filter droppable containers to only include those with valid bounding rects
const validDroppableContainers = droppableContainers.filter((container) => {
const rect = container.rect.current;
if (!rect) return false;
// Check if all required properties exist and are numbers
return (
typeof rect.top === "number" &&
typeof rect.left === "number" &&
typeof rect.right === "number" &&
typeof rect.bottom === "number" &&
typeof rect.width === "number" &&
typeof rect.height === "number"
);
});
// If all containers were filtered out, return empty array (no collision)
if (validDroppableContainers.length === 0) {
return [];
}
// Call the original closestCenter with validated containers
return closestCenter({
...args,
droppableContainers: validDroppableContainers,
});
};
interface ResponseTableProps {
data: TResponseTableData[];
survey: TSurvey;
@@ -265,7 +228,7 @@ export const ResponseTable = ({
<div>
<DndContext
id="response-table"
collisionDetection={safariSafeClosestCenter}
collisionDetection={closestCenter}
modifiers={[restrictToHorizontalAxis]}
onDragEnd={handleDragEnd}
sensors={sensors}>

View File

@@ -1,7 +1,6 @@
"use client";
import {
type CollisionDetection,
DndContext,
type DragEndEvent,
KeyboardSensor,
@@ -34,42 +33,6 @@ import { deleteContactAttributeKeyAction } from "../actions";
import { generateAttributeTableColumns } from "./attribute-table-column";
import { EditAttributeModal } from "./edit-attribute-modal";
/**
* Safari-safe collision detection that validates getBoundingClientRect() properties
* before using them. Safari can sometimes return DOMRect objects with undefined
* properties (particularly 'top'), which causes dnd-kit to throw TypeErrors.
*/
const safariSafeClosestCenter: CollisionDetection = (args) => {
const { droppableContainers } = args;
// Filter droppable containers to only include those with valid bounding rects
const validDroppableContainers = droppableContainers.filter((container) => {
const rect = container.rect.current;
if (!rect) return false;
// Check if all required properties exist and are numbers
return (
typeof rect.top === "number" &&
typeof rect.left === "number" &&
typeof rect.right === "number" &&
typeof rect.bottom === "number" &&
typeof rect.width === "number" &&
typeof rect.height === "number"
);
});
// If all containers were filtered out, return empty array (no collision)
if (validDroppableContainers.length === 0) {
return [];
}
// Call the original closestCenter with validated containers
return closestCenter({
...args,
droppableContainers: validDroppableContainers,
});
};
interface AttributesTableProps {
contactAttributeKeys: TContactAttributeKey[];
isReadOnly: boolean;
@@ -267,7 +230,7 @@ export const AttributesTable = ({
return (
<div className="w-full">
<DndContext
collisionDetection={safariSafeClosestCenter}
collisionDetection={closestCenter}
modifiers={[restrictToHorizontalAxis]}
onDragEnd={handleDragEnd}
sensors={sensors}>

View File

@@ -1,7 +1,6 @@
"use client";
import {
type CollisionDetection,
DndContext,
type DragEndEvent,
KeyboardSensor,
@@ -33,42 +32,6 @@ import { Table, TableBody, TableCell, TableHeader, TableRow } from "@/modules/ui
import { TContactTableData } from "../types/contact";
import { generateContactTableColumns } from "./contact-table-column";
/**
* Safari-safe collision detection that validates getBoundingClientRect() properties
* before using them. Safari can sometimes return DOMRect objects with undefined
* properties (particularly 'top'), which causes dnd-kit to throw TypeErrors.
*/
const safariSafeClosestCenter: CollisionDetection = (args) => {
const { droppableContainers } = args;
// Filter droppable containers to only include those with valid bounding rects
const validDroppableContainers = droppableContainers.filter((container) => {
const rect = container.rect.current;
if (!rect) return false;
// Check if all required properties exist and are numbers
return (
typeof rect.top === "number" &&
typeof rect.left === "number" &&
typeof rect.right === "number" &&
typeof rect.bottom === "number" &&
typeof rect.width === "number" &&
typeof rect.height === "number"
);
});
// If all containers were filtered out, return empty array (no collision)
if (validDroppableContainers.length === 0) {
return [];
}
// Call the original closestCenter with validated containers
return closestCenter({
...args,
droppableContainers: validDroppableContainers,
});
};
interface ContactsTableProps {
data: TContactTableData[];
fetchNextPage: () => void;
@@ -261,7 +224,7 @@ export const ContactsTable = ({
return (
<div className="w-full">
<DndContext
collisionDetection={safariSafeClosestCenter}
collisionDetection={closestCenter}
modifiers={[restrictToHorizontalAxis]}
onDragEnd={handleDragEnd}
sensors={sensors}>

View File

@@ -1,7 +1,6 @@
"use client";
import {
type CollisionDetection,
DndContext,
DragEndEvent,
PointerSensor,
@@ -57,42 +56,6 @@ import { MultiLanguageCard } from "@/modules/survey/multi-language-surveys/compo
import { ConfirmationModal } from "@/modules/ui/components/confirmation-modal";
import { isEndingCardValid, isWelcomeCardValid, validateElement } from "../lib/validation";
/**
* Safari-safe collision detection that validates getBoundingClientRect() properties
* before using them. Safari can sometimes return DOMRect objects with undefined
* properties (particularly 'top'), which causes dnd-kit to throw TypeErrors.
*/
const safariSafeClosestCorners: CollisionDetection = (args) => {
const { droppableContainers } = args;
// Filter droppable containers to only include those with valid bounding rects
const validDroppableContainers = droppableContainers.filter((container) => {
const rect = container.rect.current;
if (!rect) return false;
// Check if all required properties exist and are numbers
return (
typeof rect.top === "number" &&
typeof rect.left === "number" &&
typeof rect.right === "number" &&
typeof rect.bottom === "number" &&
typeof rect.width === "number" &&
typeof rect.height === "number"
);
});
// If all containers were filtered out, return empty array (no collision)
if (validDroppableContainers.length === 0) {
return [];
}
// Call the original closestCorners with validated containers
return closestCorners({
...args,
droppableContainers: validDroppableContainers,
});
};
interface ElementsViewProps {
localSurvey: TSurvey;
setLocalSurvey: React.Dispatch<SetStateAction<TSurvey>>;
@@ -900,7 +863,7 @@ export const ElementsView = ({
id="blocks"
sensors={sensors}
onDragEnd={onBlockCardDragEnd}
collisionDetection={safariSafeClosestCorners}>
collisionDetection={closestCorners}>
<BlocksDroppable
localSurvey={localSurvey}
setLocalSurvey={setLocalSurvey}
@@ -940,7 +903,7 @@ export const ElementsView = ({
id="endings"
sensors={sensors}
onDragEnd={onEndingCardDragEnd}
collisionDetection={safariSafeClosestCorners}>
collisionDetection={closestCorners}>
<SortableContext items={localSurvey.endings} strategy={verticalListSortingStrategy}>
{localSurvey.endings.map((ending, index) => {
return (

View File

@@ -163,9 +163,6 @@ export const MultiLanguageCard: FC<MultiLanguageCardProps> = ({
}
} else {
setIsMultiLanguageActivated(true);
if (!open) {
setOpen(true);
}
}
};

View File

@@ -1,7 +1,6 @@
"use client";
import {
type CollisionDetection,
DndContext,
DragEndEvent,
PointerSensor,
@@ -25,42 +24,6 @@ import {
} from "@/modules/ui/components/dialog";
import { DataTableSettingsModalItem } from "./data-table-settings-modal-item";
/**
* Safari-safe collision detection that validates getBoundingClientRect() properties
* before using them. Safari can sometimes return DOMRect objects with undefined
* properties (particularly 'top'), which causes dnd-kit to throw TypeErrors.
*/
const safariSafeClosestCorners: CollisionDetection = (args) => {
const { droppableContainers } = args;
// Filter droppable containers to only include those with valid bounding rects
const validDroppableContainers = droppableContainers.filter((container) => {
const rect = container.rect.current;
if (!rect) return false;
// Check if all required properties exist and are numbers
return (
typeof rect.top === "number" &&
typeof rect.left === "number" &&
typeof rect.right === "number" &&
typeof rect.bottom === "number" &&
typeof rect.width === "number" &&
typeof rect.height === "number"
);
});
// If all containers were filtered out, return empty array (no collision)
if (validDroppableContainers.length === 0) {
return [];
}
// Call the original closestCorners with validated containers
return closestCorners({
...args,
droppableContainers: validDroppableContainers,
});
};
interface DataTableSettingsModalProps<T> {
open: boolean;
setOpen: (open: boolean) => void;
@@ -103,7 +66,7 @@ export const DataTableSettingsModal = <T,>({
id="table-settings"
sensors={sensors}
onDragEnd={handleDragEnd}
collisionDetection={safariSafeClosestCorners}>
collisionDetection={closestCorners}>
<SortableContext items={columnOrder} strategy={verticalListSortingStrategy}>
{columnOrder.map((columnId) => {
if (columnId === "select" || columnId === "createdAt") return;

View File

@@ -40,21 +40,29 @@ export const SurveyInline = (props: Omit<SurveyContainerProps, "containerId">) =
isLoadingScript = true;
try {
const scriptUrl = props.appUrl ? `${props.appUrl}/js/surveys.umd.cjs` : "/js/surveys.umd.cjs";
const response = await fetch(
scriptUrl,
process.env.NODE_ENV === "development" ? { cache: "no-store" } : {}
);
if (!response.ok) {
throw new Error("Failed to load the surveys package");
}
// Load the script directly via src to ensure proper execution
// This approach works with CSP and doesn't require blob URLs or eval
await new Promise<void>((resolve, reject) => {
const scriptElement = document.createElement("script");
scriptElement.src = scriptUrl;
scriptElement.type = "text/javascript";
const scriptContent = await response.text();
const scriptElement = document.createElement("script");
// Add cache-busting in development to ensure fresh script loads
if (process.env.NODE_ENV === "development") {
scriptElement.src += `?t=${Date.now()}`;
}
scriptElement.textContent = scriptContent;
scriptElement.onload = () => {
resolve();
};
scriptElement.onerror = () => {
reject(new Error("Failed to load the surveys package"));
};
document.head.appendChild(scriptElement);
});
document.head.appendChild(scriptElement);
setIsScriptLoaded(true);
hasLoadedRef.current = true;
} catch (error) {

View File

@@ -269,6 +269,7 @@ test.describe("Multi Language Survey Create", async () => {
await page.getByText("Start from scratch").click();
await page.getByRole("button", { name: "Create survey", exact: true }).click();
await page.locator("#multi-lang-toggle").click();
await page.getByText("Multiple languages").click();
await page.getByRole("combobox").click();
await page.getByLabel("English (en)").click();
await page.getByRole("button", { name: "Confirm" }).click();